├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── fmt.yml │ ├── licenses_and_advisories.yml │ ├── linter.yml │ ├── test.yml │ └── msrv.yml ├── parameterized-macro ├── tests │ ├── fail │ │ ├── not_a_fn.stderr │ │ ├── not_a_fn.rs │ │ ├── no_param.rs │ │ ├── square_brackets.rs │ │ ├── no_argument.rs │ │ ├── square_brackets_old_error_message.rs │ │ ├── id_already_defined.rs │ │ ├── no_param_nr2.rs │ │ ├── square_brackets.stderr │ │ ├── inequal_amount_of_arg.rs │ │ ├── input_param_order_in_err_message.rs │ │ ├── inequal_amount_of_arg_order.rs │ │ ├── square_brackets_old_error_message.stderr │ │ ├── no_argument.stderr │ │ ├── no_param.stderr │ │ ├── id_already_defined.stderr │ │ ├── no_param_nr2.stderr │ │ ├── multiple_custom_test_attributes.rs │ │ ├── multiple_custom_test_attributes.stderr │ │ ├── inequal_amount_of_arg.stderr │ │ ├── inequal_amount_of_arg_order.stderr │ │ ├── input_param_order_in_err_message.stderr │ │ ├── on_visibility.rs │ │ └── on_visibility.stderr │ ├── ok │ │ ├── 20_empty.rs │ │ ├── 01_import.rs │ │ ├── 08_neg.rs │ │ ├── 13_import_rename.rs │ │ ├── 09_option.rs │ │ ├── 10_result.rs │ │ ├── 02_multiple_ids.rs │ │ ├── 14_transitive_attr.rs │ │ ├── 03_multiline.rs │ │ ├── 16_trailing_comma1.rs │ │ ├── 17_trailing_comma2.rs │ │ ├── 18_trailing_comma3.rs │ │ ├── 11_enum.rs │ │ ├── 15_comment_in_test_case.rs │ │ ├── 21_custom_test_attribute.rs │ │ ├── 19_trailing_comma4.rs │ │ ├── 22_custom_test_attribute_complex_meta.rs │ │ ├── 07_vis2.rs │ │ ├── 06_vis.rs │ │ ├── 12_enum_with_variant_value.rs │ │ └── 04_many_arg.rs │ ├── todo │ │ ├── 05_takes_fn.rs │ │ └── incorrect_parameter_type.rs │ └── cases.rs ├── src │ ├── lib.rs │ ├── tests.rs │ ├── attribute.rs │ └── generation.rs ├── Cargo.toml └── readme.md ├── .gitignore ├── deny.toml ├── Cargo.toml ├── LICENSE-MIT ├── CHANGELOG.md ├── src └── lib.rs ├── README.md ├── LICENSE-APACHE └── Cargo.lock.msrv /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ["foresterre"] 2 | buy_me_a_coffee: "foresterre" 3 | thanks_dev: "u/gh/foresterre" 4 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/not_a_fn.stderr: -------------------------------------------------------------------------------- 1 | error: expected `fn` 2 | --> $DIR/not_a_fn.rs:4:1 3 | | 4 | 4 | struct A; 5 | | ^^^^^^ 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | !Cargo.lock.msrv 5 | 6 | docs/ 7 | docs.urls 8 | 9 | *.iml 10 | .idea/ 11 | .vscode/ -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/not_a_fn.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized(v = { 1, 2 })] 4 | struct A; 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/20_empty.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized(v = {})] 4 | fn my_test(_v: i32) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/01_import.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized(v = { 1, 2 })] 4 | fn my_test(v: i32) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/08_neg.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized(v = { -1, 2 })] 4 | fn my_test(v: i32) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [licenses] 2 | confidence-threshold = 0.925 3 | allow = [ 4 | "Apache-2.0", 5 | "MIT", 6 | "Unicode-3.0", 7 | ] 8 | 9 | [advisories] 10 | 11 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/no_param.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized(y = { 1, 2, 3 })] 4 | fn my_test(x: i32) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/13_import_rename.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized as pm; 2 | 3 | #[pm(v = { (), () })] 4 | fn my_test(v: ()) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/square_brackets.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized(v = [1, 2, 3])] 4 | fn my_test(v: i32) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/no_argument.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized(x = { 0, 1 })] 4 | fn my_test(x: i32, y: i32) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/09_option.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized(v = { Some(-1), None })] 4 | fn my_test(v: Option) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/10_result.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized(v = { OK(()), Err(()) })] 4 | fn my_test(v: Result<(), ()>) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/square_brackets_old_error_message.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized(v = [1, 2, 3])] 4 | fn my_test(v: i32) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/02_multiple_ids.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized(v = {"a", "b"}, w={1,2})] 4 | fn my_test(v: &str, w: i32) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/id_already_defined.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized(v = { 1, 2, 3 }, v = { 1, 2, 3 })] 4 | fn my_test(v: i32) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/no_param_nr2.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized(x = { 1, 2, 3 }, y = { 1, 2, 3 })] 4 | fn my_test(x: i32, x2: i32) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/square_brackets.stderr: -------------------------------------------------------------------------------- 1 | error: expected curly braces 2 | --> tests/fail/square_brackets.rs:3:21 3 | | 4 | 3 | #[parameterized(v = [1, 2, 3])] 5 | | ^ 6 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/14_transitive_attr.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized(number = { 1, 2, 3 })] 4 | #[should_panic] 5 | fn my_test(number: i32) {} 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/inequal_amount_of_arg.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized(v = { "a", "b" }, w = { 1, 2, 3 })] 4 | pub(crate) fn my_test(v: &str, w: i32) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/03_multiline.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized( 4 | v = {"a", "b"}, 5 | w={1,2} 6 | )] 7 | fn my_test(v: &str, w: i32) {} 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | labels: 9 | - C-dependency-update 10 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/input_param_order_in_err_message.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized(zzz = { "a", "b" }, aaa = { 1, 2, 3 })] 4 | pub(crate) fn my_test(v: &str, w: i32) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/inequal_amount_of_arg_order.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized(b = { "a", "b", "c", "d" }, a = { 1, 2, 3 })] 4 | pub(crate) fn my_test(b: &str, a: i32) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/square_brackets_old_error_message.stderr: -------------------------------------------------------------------------------- 1 | error: expected curly braces 2 | --> $DIR/square_brackets_old_error_message.rs:3:21 3 | | 4 | 3 | #[parameterized(v = [1, 2, 3])] 5 | | ^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/16_trailing_comma1.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | // a trailing comma after v's arguments 4 | #[parameterized( 5 | v = { 1, 2 }, 6 | )] 7 | fn my_test(v: u32) {} 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/17_trailing_comma2.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | // a trailing comma after the argument list 4 | #[parameterized( 5 | v = { 1, 2, } 6 | )] 7 | fn my_test(v: u32) {} 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/18_trailing_comma3.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | // a trailing comma after w's arguments (multiple inputs) 4 | #[parameterized( 5 | v = { 1, 2 }, 6 | w = { 1, 2 }, 7 | )] 8 | fn my_test(v: u32, w: u32) {} 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/11_enum.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | enum Color { 4 | Red, 5 | Yellow, 6 | Blue, 7 | } 8 | 9 | #[parameterized(v = { Color::Red, Color::Yellow, Color::Blue, Color::Red })] 10 | fn my_test(v: Color) {} 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/15_comment_in_test_case.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized( 4 | v = {"a", "b"}, // test with &str inputs 5 | w = {1, 2} // test with i32 inputs 6 | )] 7 | fn my_test(v: &str, w: i32) {} 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/no_argument.stderr: -------------------------------------------------------------------------------- 1 | error: custom attribute panicked 2 | --> $DIR/no_argument.rs:3:1 3 | | 4 | 3 | #[parameterized(x = { 0, 1 })] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = help: message: parameterized-macro: error: Unable to find value for parameter 'y' (case #0) 8 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/no_param.stderr: -------------------------------------------------------------------------------- 1 | error: custom attribute panicked 2 | --> $DIR/no_param.rs:3:1 3 | | 4 | 3 | #[parameterized(y = { 1, 2, 3 })] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = help: message: parameterized-macro: error: Unable to find value for parameter 'x' (case #0) 8 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/21_custom_test_attribute.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized( 4 | v = { 1, 2, 3, }, 5 | w = { 1, 2, 3, }, 6 | )] 7 | #[parameterized_macro(tokio::test)] 8 | async fn my_test(v: u32, w: u32) { 9 | assert!(true); 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/id_already_defined.stderr: -------------------------------------------------------------------------------- 1 | error: custom attribute panicked 2 | --> $DIR/id_already_defined.rs:3:1 3 | | 4 | 3 | #[parameterized(v = { 1, 2, 3 }, v = { 1, 2, 3 })] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = help: message: parameterized-macro: error: found duplicate entry for 'v' 8 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/19_trailing_comma4.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | // a trailing comma after v and w's arguments (multiple inputs) and after every attribute list 4 | #[parameterized( 5 | v = { 1, 2, 3, }, 6 | w = { 1, 2, 3, }, 7 | )] 8 | fn my_test(v: u32, w: u32) {} 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/no_param_nr2.stderr: -------------------------------------------------------------------------------- 1 | error: custom attribute panicked 2 | --> $DIR/no_param_nr2.rs:3:1 3 | | 4 | 3 | #[parameterized(x = { 1, 2, 3 }, y = { 1, 2, 3 })] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = help: message: parameterized-macro: error: Unable to find value for parameter 'x2' (case #0) 8 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/multiple_custom_test_attributes.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized( 4 | v = { 1, 2, 3, }, 5 | w = { 1, 2, 3, }, 6 | )] 7 | #[parameterized_macro(macro1)] 8 | #[parameterized_macro(macro2)] 9 | async fn my_test(v: u32, w: u32) { 10 | assert!(true); 11 | } 12 | 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/22_custom_test_attribute_complex_meta.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized( 4 | v = { 1, 2, 3, }, 5 | w = { 1, 2, 3, }, 6 | )] 7 | #[parameterized_macro(tokio::test(flavor = "multi_thread", worker_threads = 1))] 8 | async fn my_test(v: u32, w: u32) { 9 | assert!(true); 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/multiple_custom_test_attributes.stderr: -------------------------------------------------------------------------------- 1 | error: custom attribute panicked 2 | --> tests/fail/multiple_custom_test_attributes.rs:3:1 3 | | 4 | 3 | / #[parameterized( 5 | 4 | | v = { 1, 2, 3, }, 6 | 5 | | w = { 1, 2, 3, }, 7 | 6 | | )] 8 | | |__^ 9 | | 10 | = help: message: parameterized-macro: the #[parameterized_macro(..)] attribute should not be present more than once! 11 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/07_vis2.rs: -------------------------------------------------------------------------------- 1 | // see also 'tests/fail/on_visibility.rs' 2 | 3 | #[macro_use] 4 | extern crate parameterized_macro; 5 | 6 | pub mod a { 7 | #[parameterized(v = { Some(- 1), None })] 8 | pub(in crate::b) fn my_test(v: Option) {} 9 | } 10 | 11 | mod b { 12 | #[cfg(test)] 13 | fn call() { 14 | a::my_test::case_0(); // this is ok 15 | } 16 | } 17 | 18 | fn main() {} 19 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/inequal_amount_of_arg.stderr: -------------------------------------------------------------------------------- 1 | error: custom attribute panicked 2 | --> tests/fail/inequal_amount_of_arg.rs:3:1 3 | | 4 | 3 | #[parameterized(v = { "a", "b" }, w = { 1, 2, 3 })] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = help: message: parameterized-macro error: Each test-case parameter should have an equal amount of values passed to it. 8 | Expected 2 arguments for 'w', but got: 3 9 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/inequal_amount_of_arg_order.stderr: -------------------------------------------------------------------------------- 1 | error: custom attribute panicked 2 | --> tests/fail/inequal_amount_of_arg_order.rs:3:1 3 | | 4 | 3 | #[parameterized(b = { "a", "b", "c", "d" }, a = { 1, 2, 3 })] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = help: message: parameterized-macro error: Each test-case parameter should have an equal amount of values passed to it. 8 | Expected 4 arguments for 'a', but got: 3 9 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/input_param_order_in_err_message.stderr: -------------------------------------------------------------------------------- 1 | error: custom attribute panicked 2 | --> tests/fail/input_param_order_in_err_message.rs:3:1 3 | | 4 | 3 | #[parameterized(zzz = { "a", "b" }, aaa = { 1, 2, 3 })] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = help: message: parameterized-macro error: Each test-case parameter should have an equal amount of values passed to it. 8 | Expected 2 arguments for 'aaa', but got: 3 9 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/on_visibility.rs: -------------------------------------------------------------------------------- 1 | // see also 'tests/ok/07_vis2.rs' 2 | 3 | #[macro_use] 4 | extern crate parameterized_macro; 5 | 6 | pub mod a { 7 | #[parameterized(v = { Some(-1), None })] 8 | pub(in crate::b) fn my_test(v: Option) {} 9 | } 10 | 11 | mod b { 12 | #[cfg(test)] 13 | fn call() { 14 | a::my_test::case_0(); // this is ok 15 | } 16 | } 17 | 18 | fn main() { 19 | if cfg!(test) { 20 | a::my_test::case_0(); // this isn't ok 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/06_vis.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | // we want to copy the visibility from the fn 4 | 5 | #[parameterized(v = { "a", "b" }, w = { 1, 2 })] 6 | pub(crate) fn my_test(v: &str, w: i32) {} 7 | 8 | // which should generate (something along these lines): 9 | // 10 | // ``` 11 | // pub(crate) mod fn my_test { 12 | // #[test] 13 | // pub(crate) my_test_0(v: &str, w: i32) {} 14 | // 15 | // #[test] 16 | // pub(crate) my_test_0(v: &str, w: i32) {} 17 | // } 18 | // ``` 19 | 20 | fn main() {} 21 | -------------------------------------------------------------------------------- /.github/workflows/fmt.yml: -------------------------------------------------------------------------------- 1 | name: "ci-fmt" 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | - staging # for Bors 9 | - trying # for Bors 10 | jobs: 11 | fmt: 12 | name: fmt 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: checkout_repository 16 | uses: actions/checkout@v3 17 | 18 | - name: install_rust 19 | uses: dtolnay/rust-toolchain@stable 20 | with: 21 | components: rustfmt 22 | 23 | - name: check_formatting 24 | run: cargo fmt --all -- --check 25 | -------------------------------------------------------------------------------- /parameterized-macro/tests/fail/on_visibility.stderr: -------------------------------------------------------------------------------- 1 | error[E0433]: failed to resolve: could not find `my_test` in `a` 2 | --> tests/fail/on_visibility.rs:20:12 3 | | 4 | 20 | a::my_test::case_0(); // this isn't ok 5 | | ^^^^^^^ could not find `my_test` in `a` 6 | | 7 | note: found an item that was configured out 8 | --> tests/fail/on_visibility.rs:8:25 9 | | 10 | 7 | #[parameterized(v = { Some(-1), None })] 11 | | ---------------------------------------- the item is gated here 12 | 8 | pub(in crate::b) fn my_test(v: Option) {} 13 | | ^^^^^^^ 14 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/12_enum_with_variant_value.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | enum Color { 4 | Red(Pigment), 5 | Yellow, 6 | Blue(Pigment), 7 | } 8 | 9 | struct Pigment { 10 | material_id: u32, 11 | } 12 | 13 | impl Pigment { 14 | fn new(id: u32) -> Self { 15 | Self { material_id: id } 16 | } 17 | } 18 | 19 | impl Default for Pigment { 20 | fn default() -> Self { 21 | Self { material_id: 0 } 22 | } 23 | } 24 | 25 | #[parameterized(v = { Color::Red(Pigment::new(5)), Color::Yellow, Color::Blue::default(), Color::Red(Pigment { 26 | material_id: 8 27 | }) })] 28 | fn my_test(v: Color) {} 29 | 30 | fn main() {} 31 | -------------------------------------------------------------------------------- /.github/workflows/licenses_and_advisories.yml: -------------------------------------------------------------------------------- 1 | name: "ci-licenses_and_advisories" 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | - staging # for Bors 9 | - trying # for Bors 10 | jobs: 11 | licenses_and_advisories: 12 | name: licenses_and_advisories 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | checks: 17 | - advisories 18 | - bans licenses sources 19 | 20 | continue-on-error: ${{ matrix.checks == 'advisories' }} 21 | steps: 22 | - uses: actions/checkout@v4 23 | - uses: EmbarkStudios/cargo-deny-action@v2 24 | with: 25 | command: check ${{ matrix.checks }} 26 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: "ci-linter" 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | - staging # for Bors 9 | - trying # for Bors 10 | jobs: 11 | linter: 12 | name: linter 13 | runs-on: ubuntu-latest 14 | continue-on-error: true 15 | steps: 16 | - name: checkout_repo 17 | uses: actions/checkout@v3 18 | 19 | - name: install_rust 20 | uses: dtolnay/rust-toolchain@stable 21 | with: 22 | components: clippy 23 | 24 | - name: check_clippy 25 | uses: actions-rs/clippy-check@v1 26 | with: 27 | token: ${{ secrets.GITHUB_TOKEN }} 28 | args: --all-features --all-targets --workspace 29 | -------------------------------------------------------------------------------- /parameterized-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate syn; 3 | extern crate core; 4 | extern crate proc_macro; 5 | 6 | mod attribute; 7 | mod generation; 8 | mod tests; 9 | 10 | #[proc_macro_attribute] 11 | pub fn parameterized( 12 | args: ::proc_macro::TokenStream, 13 | input: ::proc_macro::TokenStream, 14 | ) -> ::proc_macro::TokenStream { 15 | impl_macro(args, input) 16 | } 17 | 18 | fn impl_macro( 19 | args: ::proc_macro::TokenStream, 20 | input: ::proc_macro::TokenStream, 21 | ) -> ::proc_macro::TokenStream { 22 | let argument_lists = parse_macro_input!(args as attribute::ParameterizedList); 23 | let func = parse_macro_input!(input as attribute::Fn); 24 | 25 | generation::generate(argument_lists, func) 26 | } 27 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parameterized" 3 | version = "2.1.0" 4 | authors = ["Martijn Gribnau "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | description = "Procedural macro which brings a compact parameterized testing implementation to Rust (inspired by JUnit @ParameterizedTest)" 8 | documentation = "https://docs.rs/crate/parameterized" 9 | repository = "https://github.com/foresterre/parameterized" 10 | readme = "README.md" 11 | keywords = ["parameterized", "parametrized", "test", "unit-test", "junit"] 12 | categories = ["development-tools", "development-tools::testing"] 13 | rust-version = "1.68" 14 | 15 | [features] 16 | # not semver protected 17 | __unstable_square-brackets-old-error-message = ["parameterized-macro/__unstable_square-brackets-old-error-message"] 18 | 19 | [workspace] 20 | members = ["parameterized-macro"] 21 | 22 | [dependencies] 23 | parameterized-macro = { path = "parameterized-macro", version = "~3.0.0" } 24 | 25 | [dev-dependencies] 26 | tokio = { version = "1", features = ["full"] } 27 | -------------------------------------------------------------------------------- /parameterized-macro/tests/ok/04_many_arg.rs: -------------------------------------------------------------------------------- 1 | use parameterized_macro::parameterized; 2 | 3 | #[parameterized( 4 | v = {"a", "b", "c", "d", "e", "f", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u"}, 5 | w = {-1, -2, -3, -4, -5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, 6 | a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, -20}, 7 | b = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, -20}, 8 | c = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, -20}, 9 | d = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, 10 | e = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, 11 | f = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, 12 | g = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, 13 | )] 14 | fn my_test(v: &str, w: i64, a: i32, b: i16, c: i8, d: u64, e: u32, f: u16, g: u8) { 15 | assert!(true); 16 | } 17 | 18 | fn main() {} 19 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Martijn Gribnau 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /parameterized-macro/tests/todo/05_takes_fn.rs: -------------------------------------------------------------------------------- 1 | // fixme: we don't support this feature yet, so anything below is speculation about what it might 2 | // look like 3 | //use parameterized_macro::parameterized; 4 | // 5 | //// Vec or something else that implements IntoIter 6 | //fn f() -> Vec> { 7 | // vec![Some(1), Some(2)] 8 | //} 9 | // 10 | //fn g() -> Vec> { 11 | // vec![Err(())] 12 | //} 13 | // 14 | ///// Say we define a function with identifier `five`: 15 | ///// ``` 16 | ///// fn five() -> Vec { vec![5, 5, 5, 5] } 17 | ///// ``` 18 | ///// To write a parameterized test which uses this function to generate inputs, 19 | ///// we take the id of the function: `five` and use that in our test case: 20 | ///// 21 | ///// ``` 22 | ///// #[parameterized(fn = five)] 23 | ///// fn my_test_case(five: i8) { 24 | ///// assert!(five == 5) 25 | ///// } 26 | ///// ``` 27 | ///// The parameter of your defined test case function should be the Item value (T) of the 28 | ///// IntoIterator. 29 | ///// 30 | //#[parameterized(fn = {f, g})] 31 | //fn my_test(f: Option, g: Result) { 32 | // assert!(f.is_ok() && g.is_err()); 33 | //} 34 | // 35 | fn main() {} 36 | -------------------------------------------------------------------------------- /parameterized-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parameterized-macro" 3 | version = "3.0.0" 4 | authors = ["Martijn Gribnau "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | description = "Attribute macro crate for parameterized tests." 8 | documentation = "https://docs.rs/parameterized-macro" 9 | repository = "https://github.com/foresterre/parameterized" 10 | readme = "readme.md" 11 | keywords = ["parameterized", "attribute", "test", "unit-test", "junit"] 12 | categories = ["development-tools", "development-tools::testing"] 13 | rust-version = "1.68" 14 | 15 | autotests = false 16 | 17 | [lib] 18 | proc-macro = true 19 | 20 | [[test]] 21 | name = "tests" 22 | path = "tests/cases.rs" 23 | 24 | [dependencies] 25 | fnv = "1.0.7" 26 | # from indexmap 2.12 the MSRV is 1.82; unless indexmap needs security updates, a lower MSRV is preferable, since parameterized doesn't need it 27 | indexmap = { version = "~2.11.4", default-features = false } 28 | proc-macro2 = "1.0.24" 29 | quote = "1.0.8" 30 | syn = { version = "2.0.52", features = ["full"] } 31 | 32 | [dev-dependencies] 33 | trybuild = "1.0.113" 34 | 35 | [features] 36 | # not semver protected 37 | __unstable_square-brackets-old-error-message = [] 38 | -------------------------------------------------------------------------------- /parameterized-macro/tests/todo/incorrect_parameter_type.rs: -------------------------------------------------------------------------------- 1 | // fixme: presumably the following test won't succeed because the generated syntax is correct; 2 | // we need to run type checking before we know that this won't compile 3 | // 4 | // If you move this test case to the 'expand' workspace crate, which is used to manually inspect 5 | // outcomes, you'll find that upon running `cargo test`, type checking does fail indeed. 6 | // If you inspect the token tree manually, you will also find that the 'expected cases' described below 7 | // are indeed generated correctly. 8 | 9 | //#[cfg_attr(not(test), allow(unused))] 10 | //use parameterized_macro::parameterized; 11 | // 12 | //#[cfg_attr(not(test), allow(unused))] 13 | //enum Color { 14 | // Red, 15 | // Yellow, 16 | // Blue, 17 | //} 18 | // 19 | //#[cfg_attr(not(test), allow(unused))] 20 | //enum NotAColor {} 21 | // 22 | //#[parameterized(v = { Color::Red, Color::Yellow })] 23 | //fn my_test(v: NotAColor) {} 24 | 25 | // expected cases: 26 | // ``` 27 | // fn #fn_ident () { 28 | // let v: NotAColor = Color::Red; 29 | // {} // body 30 | // } 31 | // 32 | // fn #fn_ident () { 33 | // let v: NotAColor = Color::Yellow; 34 | // {} // body 35 | // } 36 | // ``` 37 | 38 | fn main() {} 39 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "ci-test" 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | - staging # for Bors 9 | - trying # for Bors 10 | jobs: 11 | test: 12 | name: test 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | build: [ubuntu, macos, win-gnu, win-msvc] 18 | include: 19 | - build: ubuntu 20 | os: ubuntu-latest 21 | rust: stable 22 | 23 | - build: macos 24 | os: macOS-latest 25 | rust: stable 26 | 27 | - build: win-gnu 28 | os: windows-latest 29 | rust: stable-x86_64-gnu 30 | 31 | - build: win-msvc 32 | os: windows-latest 33 | rust: stable 34 | 35 | steps: 36 | - name: checkout_repository 37 | uses: actions/checkout@v3 38 | 39 | - name: install_rust 40 | uses: dtolnay/rust-toolchain@master 41 | with: 42 | toolchain: ${{ matrix.rust }} 43 | 44 | - name: fetch 45 | run: cargo fetch --verbose 46 | 47 | - name: build 48 | run: cargo build --verbose 49 | 50 | - name: test_all 51 | run: cargo test --verbose --all 52 | -------------------------------------------------------------------------------- /.github/workflows/msrv.yml: -------------------------------------------------------------------------------- 1 | name: "ci-msrv" 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | - staging # for Bors 9 | - trying # for Bors 10 | schedule: 11 | - cron: '00 05 * * *' 12 | jobs: 13 | msrv: 14 | name: msrv 15 | runs-on: ubuntu-latest 16 | continue-on-error: true 17 | steps: 18 | - name: checkout_repo 19 | uses: actions/checkout@v3 20 | 21 | - name: install_rust 22 | uses: dtolnay/rust-toolchain@stable 23 | 24 | - name: install_cargo_msrv 25 | run: cargo install --no-default-features cargo-msrv 26 | 27 | - name: version_of_cargo_msrv 28 | run: cargo msrv --version 29 | 30 | - name: show_msrv 31 | run: cargo msrv show --output-format minimal 32 | 33 | - name: use_msrv_lock_file 34 | run: cp Cargo.lock.msrv Cargo.lock 35 | 36 | - name: run_cargo_msrv_for_macro 37 | run: cargo msrv verify --manifest-path ./parameterized-macro/Cargo.toml --output-format json -- cargo check --locked 38 | 39 | - name: run_cargo_msrv_on_verify_failure_for_macro 40 | if: ${{ failure() }} 41 | run: cargo msrv find --manifest-path ./parameterized-macro/Cargo.toml --output-format json -- cargo check --locked 42 | 43 | - name: run_cargo_msrv 44 | run: cargo msrv verify --output-format json -- cargo check --locked 45 | 46 | - name: run_cargo_msrv_on_verify_failure 47 | if: ${{ failure() }} 48 | run: cargo msrv find --output-format json -- cargo check --locked 49 | -------------------------------------------------------------------------------- /parameterized-macro/readme.md: -------------------------------------------------------------------------------- 1 | ### High level overview of test case generation 2 | 3 | #### An example 4 | 5 | With this macro we generate the following code (1) from the given parameterized test definition (0): 6 | 7 | **parameterized test definition:** 8 | 9 | ```rust 10 | // 0 11 | 12 | #[parameterized(a = {1, 2}, b = { "wanderlust", "wanderer" })] 13 | fn my_test(a: i32, b: &str) { 14 | assert!(a > 0 && b.starts_with("w")) 15 | } 16 | ``` 17 | 18 | 19 | **generated test cases:** 20 | ```rust 21 | // 1 22 | 23 | #[cfg(test)] 24 | mod my_test { 25 | #[test] 26 | fn case_0() { 27 | let a: i32 = 1; 28 | let b: &str = "wanderlust"; 29 | assert!(a > 0 && b.starts_with("w")) 30 | } 31 | 32 | #[test] 33 | fn case_1() { 34 | let a: i32 = 2; 35 | let b: &str = "wanderer"; 36 | assert!(a > 0 && b.starts_with("w")) 37 | } 38 | } 39 | ``` 40 | 41 | More examples can be found in the `expand` crate, and the tests. 42 | 43 | #### notes: 44 | - The function name in (1) is the same as the module name in (0) 45 | 46 | - Note that arguments are not limited to primitives; they can be any expression (assuming:) 47 | 48 | - In a parameterized test case, the input arguments (which are expressions) specified in the attribute should evaluate 49 | to the same type as their identically named companions in the function signature. 50 | 51 | - Tests executed from the workspace crate should be run individually, e.g. 52 | (`cargo test --package parameterized-macro --test tests individual_cases -- --exact`). 53 | Otherwise, if just `cargo test` is used, some generated test cases will run in an incorrect context setting. 54 | -------------------------------------------------------------------------------- /parameterized-macro/tests/cases.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn individual_cases() { 3 | let t = trybuild::TestCases::new(); 4 | t.pass("tests/ok/01_import.rs"); 5 | t.pass("tests/ok/02_multiple_ids.rs"); 6 | t.pass("tests/ok/03_multiline.rs"); 7 | t.pass("tests/ok/04_many_arg.rs"); 8 | 9 | t.pass("tests/ok/06_vis.rs"); 10 | t.pass("tests/ok/07_vis2.rs"); 11 | t.pass("tests/ok/08_neg.rs"); 12 | t.pass("tests/ok/09_option.rs"); 13 | t.pass("tests/ok/10_result.rs"); 14 | t.pass("tests/ok/11_enum.rs"); 15 | t.pass("tests/ok/12_enum_with_variant_value.rs"); 16 | t.pass("tests/ok/13_import_rename.rs"); 17 | t.pass("tests/ok/14_transitive_attr.rs"); 18 | t.pass("tests/ok/15_comment_in_test_case.rs"); 19 | t.pass("tests/ok/16_trailing_comma1.rs"); 20 | t.pass("tests/ok/17_trailing_comma2.rs"); 21 | t.pass("tests/ok/18_trailing_comma3.rs"); 22 | t.pass("tests/ok/19_trailing_comma4.rs"); 23 | t.pass("tests/ok/20_empty.rs"); 24 | t.pass("tests/ok/21_custom_test_attribute.rs"); 25 | t.pass("tests/ok/22_custom_test_attribute_complex_meta.rs"); 26 | 27 | t.compile_fail("tests/fail/id_already_defined.rs"); 28 | t.compile_fail("tests/fail/inequal_amount_of_arg.rs"); 29 | t.compile_fail("tests/fail/inequal_amount_of_arg_order.rs"); 30 | t.compile_fail("tests/fail/input_param_order_in_err_message.rs"); 31 | t.compile_fail("tests/fail/not_a_fn.rs"); 32 | t.compile_fail("tests/fail/on_visibility.rs"); 33 | t.compile_fail("tests/fail/multiple_custom_test_attributes.rs"); 34 | 35 | #[cfg(not(feature = "__unstable_square-brackets-old-error-message"))] 36 | t.compile_fail("tests/fail/square_brackets.rs"); 37 | 38 | #[cfg(feature = "__unstable_square-brackets-old-error-message")] 39 | t.compile_fail("tests/fail/square_brackets_old_error_message.rs"); 40 | 41 | t.compile_fail("tests/fail/no_argument.rs"); 42 | t.compile_fail("tests/fail/no_param.rs"); 43 | t.compile_fail("tests/fail/no_param_nr2.rs"); 44 | } 45 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This is the changelog for [parameterized](https://github.com/foresterre/parameterized). Parameterized is a Rust library 4 | which provides an attribute macro, to be used for parameterized testing. 5 | 6 | If you found an issue, have a suggestion or want to provide feedback or insights, please feel free to open an issue on 7 | the [issue tracker](https://github.com/foresterre/parameterized/issues), or open a topic in 8 | the [discussions section](https://github.com/foresterre/parameterized/discussions). 9 | 10 | ## [2.1.0] - 2025-11-07 11 | 12 | ### Changed 13 | 14 | * Updated MSRV to Rust `1.68` 15 | * Updated parameterized-macro to `3.0.0` 16 | 17 | [2.1.0]: https://github.com/foresterre/parameterized/releases/tag/v2.1.0 18 | 19 | ## [2.0.0] - 2024-03-05 20 | 21 | ### Added 22 | 23 | * Added support for custom test macros (e.g., `#[tokio::test]` instead of the default `#[test]`) by specifying the 24 | `#[parameterized_macro(...)]` attribute. 25 | * `#[parameterized]` macro now parses and regenerates optional `const`, `async`, `unsafe` and return type items for `fn` 26 | definitions. 27 | 28 | ### Changed 29 | 30 | * MSRV is now `1.63` 31 | 32 | [2.0.0]: https://github.com/foresterre/parameterized/releases/tag/v2.0.0 33 | 34 | ## [1.1.0] - 2023-10-23 35 | 36 | ### Added 37 | 38 | * Add option to use return type in tests 39 | 40 | [1.1.0]: https://github.com/foresterre/parameterized/releases/tag/v1.1.0 41 | 42 | ## [1.0.1] - 2022-11-09 43 | 44 | ### Changed 45 | 46 | * Updated MSRV to Rust `1.38` 47 | * Updated parameterized-macro to `1.0.1` 48 | 49 | [1.0.1]: https://github.com/foresterre/parameterized/releases/tag/v1.0.1 50 | 51 | ## [1.0.0] - 2022-05-02 52 | 53 | ### Changed 54 | 55 | * Updated MSRV to Rust `1.36` 56 | 57 | [1.0.0]: https://github.com/foresterre/parameterized/releases/tag/v1.0.0 58 | 59 | ## [0.3.1] - 2021-01-07 60 | 61 | ### Fixed 62 | 63 | * Fix issue where parameterized-macro used the public API of the syn::export module which the docs described as 64 | a non-public implementation details module, not covered by semver 65 | 66 | [0.3.1]: https://github.com/foresterre/parameterized/releases/tag/v0.3.1 67 | 68 | ## [0.3.0] - 2020-12-30 69 | 70 | This release consists of internal changes. There are no changes for users. 71 | An alternative syntax which was added after 0.2.0, but was never released, 72 | has been moved into its own project named [Yare](https://github.com/foresterre/yare). 73 | 74 | [0.3.0]: https://github.com/foresterre/parameterized/releases/tag/v0.3.0 75 | 76 | ## [0.2.0] - 2020-06-23 77 | 78 | This release consists of internal changes. There are no changes for users. 79 | 80 | [0.2.0]: https://github.com/foresterre/parameterized/releases/tag/v0.2.0 81 | 82 | ## [0.1.0] - 2019-11-09 83 | 84 | First version published on crates.io 85 | 86 | [0.1.0]: https://github.com/foresterre/parameterized/releases/tag/v0.1.0 87 | -------------------------------------------------------------------------------- /parameterized-macro/src/tests.rs: -------------------------------------------------------------------------------- 1 | use indexmap::IndexMap; 2 | use std::fmt::Formatter; 3 | 4 | type FnvIndexMap = IndexMap; 5 | 6 | pub struct TestCases<'node> { 7 | map: FnvIndexMap<&'node syn::Ident, Vec<&'node syn::Expr>>, 8 | amount_of_test_cases: Option, 9 | } 10 | 11 | impl std::fmt::Debug for TestCases<'_> { 12 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 13 | let ids = self 14 | .map 15 | .iter() 16 | .map(|(id, _)| format!("{}", id)) 17 | .collect::>(); 18 | let joined = ids.join(", "); 19 | f.write_str(&format!("TestCases {{ identifiers = {} }}", joined)) 20 | } 21 | } 22 | 23 | impl<'node> TestCases<'node> { 24 | pub fn empty() -> Self { 25 | Self { 26 | map: IndexMap::default(), 27 | amount_of_test_cases: None, 28 | } 29 | } 30 | 31 | pub fn insert(&mut self, id: &'node syn::Ident, exprs: Vec<&'node syn::Expr>) { 32 | let expressions = exprs.len(); 33 | 34 | // 35 | match self.amount_of_test_cases { 36 | Some(amount) if amount != expressions => panic!( 37 | "parameterized-macro error: Each test-case parameter should have an equal amount of values passed to it.\n\ 38 | Expected {} arguments for '{}', but got: {}", amount, id, expressions, 39 | ), 40 | Some(_) => {} 41 | None => { 42 | self.amount_of_test_cases = Some(expressions) 43 | }, 44 | }; 45 | 46 | // 47 | if expressions != self.unwrap_amount_of_test_cases() { 48 | panic!( 49 | "parameterized-macro: error: Each test-case parameter should have an equal amount of values passed to it.\n\ 50 | Expected {} arguments for '{}', but got: {}", self.unwrap_amount_of_test_cases(), id, expressions, 51 | ); 52 | } 53 | 54 | // Only insert if the id does not yet exist 55 | if self.map.get(id).is_none() { 56 | self.map.insert(id, exprs); 57 | } else { 58 | panic!( 59 | "parameterized-macro: error: found duplicate entry for '{}'", 60 | id 61 | ); 62 | } 63 | } 64 | 65 | pub fn get(&self, id: &syn::Ident, ith: usize) -> &syn::Expr { 66 | if let Some(exprs) = self.map.get(id) { 67 | exprs[ith] 68 | } else { 69 | panic!( 70 | "parameterized-macro: error: Unable to find value for parameter '{}' (case #{})", 71 | id, ith 72 | ); 73 | } 74 | } 75 | 76 | pub fn amount_of_test_cases(&self) -> Option { 77 | self.amount_of_test_cases 78 | } 79 | 80 | // NB: Panics if amount of test cases is unknown, i.e. if we haven't used the first parameter 81 | // to seed the amount of expressions required to be defined for each parameter. 82 | // This should never happen to 'parameterized' crate users, and must be guarded against in the 83 | // places where it's used. 84 | fn unwrap_amount_of_test_cases(&self) -> usize { 85 | if let Some(amount) = self.amount_of_test_cases { 86 | amount 87 | } else { 88 | unreachable!() 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | pub use parameterized_macro::parameterized; 4 | 5 | /// Attribute macro's such as 'parameterized' do not enable the run tests intent for a module 6 | /// marked as cfg(test) (or a #[test] function for that matter) in Intellij. 7 | /// 8 | /// To enable the intent within a module, we need at least a single test marked with `#[test]`. 9 | /// The `ide!()` macro is a work around for this issue and creates this empty test. It can be called 10 | /// within every module where we wish to run test cases using the run configuration / run test context 11 | /// menu. 12 | /// 13 | /// Using the intellij-rust new macro expansion engine, if this macro is called within a module, 14 | /// the module will be marked as test, and the 'run as test' context menu will be provided in the 15 | /// gutter. 16 | #[macro_export] 17 | macro_rules! ide { 18 | () => { 19 | #[test] 20 | fn __mark_with_test_intent() {} 21 | }; 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use crate::ide; 27 | use crate::parameterized; 28 | 29 | fn add5>(component: T) -> u32 { 30 | component.into() + 5 31 | } 32 | 33 | mod readme_test { 34 | use super::*; 35 | 36 | ide!(); 37 | 38 | #[parameterized(input = { 39 | 0, 1, 2 40 | }, expected = { 41 | 5, 6, 7 42 | })] 43 | fn test_add5(input: u16, expected: u32) { 44 | assert_eq!(add5(input), expected) 45 | } 46 | } 47 | 48 | mod marked_as_test_module { 49 | use super::*; 50 | 51 | ide!(); 52 | 53 | #[parameterized(input = { 2, 3, 4 }, output = { 4, 6, 8 })] 54 | fn test_times2(input: i32, output: i32) { 55 | let times2 = |receiver: i32| receiver * 2; 56 | 57 | assert_eq!(times2(input), output); 58 | } 59 | } 60 | 61 | mod transitive_attrs { 62 | use super::*; 63 | 64 | ide!(); 65 | 66 | #[parameterized(input = { None, None, None })] 67 | #[should_panic] 68 | fn numbers(input: Option<()>) { 69 | input.unwrap() 70 | } 71 | } 72 | 73 | mod fn_signatures { 74 | use super::*; 75 | 76 | ide!(); 77 | 78 | #[parameterized(_input = { 0, 1, 2 })] 79 | const fn constness(_input: u8) { 80 | assert!(true) 81 | } 82 | 83 | #[parameterized(input = { true, true, true })] 84 | #[parameterized_macro(tokio::test)] 85 | async fn asyncness(input: bool) { 86 | assert!(input) 87 | } 88 | 89 | #[parameterized(_input = { 0, 1, 2 })] 90 | const fn return_type(_input: u8) -> () { 91 | assert!(true) 92 | } 93 | } 94 | 95 | mod custom_test_attribute { 96 | use super::*; 97 | 98 | ide!(); 99 | 100 | #[parameterized(input = { true, true, true })] 101 | #[parameterized_macro(tokio::test)] 102 | async fn tokio_simple_meta(input: bool) { 103 | assert!(input) 104 | } 105 | 106 | #[parameterized(input = { true, true, true })] 107 | #[parameterized_macro(tokio::test(flavor = "multi_thread", worker_threads = 1))] 108 | async fn tokio_complex_meta(input: bool) { 109 | assert!(input) 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /parameterized-macro/src/attribute.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use std::fmt::Formatter; 3 | use syn::parse::{Parse, ParseStream, Result}; 4 | use syn::punctuated::Punctuated; 5 | use syn::token::{Async, Const, Unsafe}; 6 | use syn::{braced, Attribute, Block, ItemFn, Meta, ReturnType, Visibility}; 7 | 8 | /// An ordered list of attribute arguments, which consists of (id, param-args) pairs. 9 | #[derive(Clone)] 10 | pub struct ParameterizedList { 11 | pub args: Punctuated, 12 | } 13 | 14 | impl Parse for ParameterizedList { 15 | /// This part parses 16 | /// It uses IdentifiedArgList.parse() for each inner argument. 17 | /// 18 | /// ['IdentifiedArgList.parse ']: struct.IdentifiedArgList 19 | fn parse(input: ParseStream) -> Result { 20 | Ok(ParameterizedList { 21 | args: Punctuated::parse_terminated(input)?, 22 | }) 23 | } 24 | } 25 | 26 | /// A single (id, param-args) pair which consists of: 27 | /// - id: identifier for the list 28 | /// - param_args: ordered list arguments formatted using curly-braced list syntax, i.e. "{ 3, 4, 5 }" 29 | /// 30 | /// For example: 31 | /// `parameter_name = { 3, 4, 5}` 32 | #[derive(Clone)] 33 | pub struct ParameterList { 34 | pub id: syn::Ident, 35 | _assignment: Token![=], 36 | _braces: syn::token::Brace, 37 | pub param_args: Punctuated, 38 | } 39 | 40 | impl std::fmt::Debug for ParameterList { 41 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 42 | f.write_str(&format!("ParameterList(id = {:?})", self.id)) 43 | } 44 | } 45 | 46 | impl Parse for ParameterList { 47 | // parts: 48 | // 49 | // v = { a, b, c } 50 | // $ident $Token![=] ${ $expr, ... } 51 | fn parse(input: ParseStream) -> Result { 52 | let content; 53 | 54 | Ok(ParameterList { 55 | id: input.parse()?, 56 | _assignment: input.parse()?, 57 | _braces: braced!(content in input), 58 | param_args: Punctuated::parse_terminated(&content)?, 59 | }) 60 | } 61 | } 62 | 63 | // TODO: add to parse, code gen of ParameterizedList 64 | pub enum MacroAttribute { 65 | /// A `#[parameterized_macro(..)]` attribute 66 | /// 67 | /// Example usage: `#[parameterized_macro(tokio::test)]` 68 | UseTestMacro(Meta), 69 | /// An attribute unrelated to this crate; to be retained after the generation step 70 | Unrelated(Attribute), 71 | } 72 | 73 | impl MacroAttribute { 74 | pub fn is_use_test_macro(&self) -> bool { 75 | matches!(self, Self::UseTestMacro(_)) 76 | } 77 | 78 | pub fn quoted(&self) -> proc_macro2::TokenStream { 79 | match self { 80 | Self::UseTestMacro(meta) => quote!(#meta), 81 | Self::Unrelated(attr) => quote!(#attr), 82 | } 83 | } 84 | } 85 | 86 | pub struct Fn { 87 | pub attrs: Vec, 88 | pub item_fn: ItemFn, 89 | } 90 | 91 | impl Parse for Fn { 92 | fn parse(input: ParseStream) -> Result { 93 | let attrs = input 94 | .call(Attribute::parse_outer)? 95 | .into_iter() 96 | .map(|attr| { 97 | if attr.path().is_ident("parameterized_macro") { 98 | attr.parse_args::().map(MacroAttribute::UseTestMacro) 99 | } else { 100 | Ok(MacroAttribute::Unrelated(attr)) 101 | } 102 | }) 103 | .collect::>>()?; 104 | 105 | Ok(Self { 106 | attrs, 107 | item_fn: input.parse()?, 108 | }) 109 | } 110 | } 111 | 112 | impl Fn { 113 | pub fn constness(&self) -> Option<&Const> { 114 | self.item_fn.sig.constness.as_ref() 115 | } 116 | 117 | pub fn asyncness(&self) -> Option<&Async> { 118 | self.item_fn.sig.asyncness.as_ref() 119 | } 120 | 121 | pub fn unsafety(&self) -> Option<&Unsafe> { 122 | self.item_fn.sig.unsafety.as_ref() 123 | } 124 | 125 | pub fn visibility(&self) -> &Visibility { 126 | &self.item_fn.vis 127 | } 128 | 129 | pub fn return_type(&self) -> &ReturnType { 130 | &self.item_fn.sig.output 131 | } 132 | 133 | pub fn body(&self) -> &Block { 134 | &self.item_fn.block 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /parameterized-macro/src/generation.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Span, TokenStream}; 2 | 3 | use crate::attribute::{Fn, ParameterizedList}; 4 | use crate::tests::TestCases; 5 | 6 | pub fn generate(argument_lists: ParameterizedList, func: Fn) -> proc_macro::TokenStream { 7 | // Map the given arguments by their identifier 8 | let values = into_argument_map(&argument_lists); 9 | let args = function_arguments(&func); 10 | let amount_of_test_cases = values.amount_of_test_cases().unwrap_or_default(); 11 | 12 | let generated_test_cases = 13 | (0..amount_of_test_cases).map(|i| generate_test_case(args.as_slice(), &values, i, &func)); 14 | 15 | generate_module(generated_test_cases, &func).into() 16 | } 17 | 18 | /// Transform an AttributeArgList into an ordered map which orders its 19 | /// elements by insertion order (assuming no elements will be removed). 20 | /// The returned map contains (identifier, argument expression list) pairs. 21 | fn into_argument_map(arguments: &ParameterizedList) -> TestCases<'_> { 22 | arguments 23 | .args 24 | .iter() 25 | .fold(TestCases::empty(), |mut acc, args| { 26 | let identifier = &args.id; 27 | let exprs = args.param_args.iter().collect::>(); 28 | 29 | acc.insert(identifier, exprs); 30 | 31 | acc 32 | }) 33 | } 34 | 35 | type FnArgPair<'ctx> = (&'ctx syn::Ident, &'ctx Box); 36 | 37 | /// Returns the vector of all typed parameter pairs for a given function. 38 | fn function_arguments(f: &Fn) -> Vec> { 39 | f.item_fn.sig.inputs.iter().map(|fn_arg| { 40 | match fn_arg { 41 | syn::FnArg::Typed(syn::PatType { pat, ty, .. }) => match pat.as_ref() { 42 | syn::Pat::Ident(syn::PatIdent { ident, .. }) => (ident, ty) , 43 | _ => panic!("parameterized-macro: error: No identifier found for test case") 44 | } 45 | _ => panic!("parameterized-macro: error: Unexpected receiver found in test case function arguments") 46 | } 47 | 48 | }).collect::>() 49 | } 50 | 51 | fn generate_module>(test_cases: I, f: &Fn) -> TokenStream { 52 | let name = &f.item_fn.sig.ident; 53 | let vis = &f.item_fn.vis; 54 | let mod_ident = syn::Ident::new(&format!("{}", name), name.span()); 55 | 56 | // we need to include `use super::*` since we put the test cases in a new module 57 | quote::quote! { 58 | #[cfg(test)] 59 | #vis mod #mod_ident { 60 | use super::*; 61 | 62 | #(#test_cases)* 63 | } 64 | } 65 | } 66 | 67 | /// Generate a single test case from the attribute inputs. 68 | fn generate_test_case( 69 | parameters: &[FnArgPair], 70 | test_cases: &TestCases, 71 | i: usize, 72 | f: &Fn, 73 | ) -> TokenStream { 74 | let constness = f.constness(); 75 | let asyncness = f.asyncness(); 76 | let unsafety = f.unsafety(); 77 | let visibility = f.visibility(); 78 | let identifier = syn::Ident::new(&format!("case_{}", i), Span::call_site()); 79 | let return_type = f.return_type(); 80 | let body = f.body(); 81 | 82 | // Construction let bindings for all parameters 83 | let bindings = parameters.iter().map(|(identifier, ty)| { 84 | let expr = test_cases.get(identifier, i); 85 | 86 | generate_binding(identifier, ty, expr) 87 | }); 88 | 89 | let (use_test_macro, unrelated_attributes): (Vec<_>, Vec<_>) = 90 | f.attrs.iter().partition(|&m| m.is_use_test_macro()); 91 | 92 | if use_test_macro.len() > 1 { 93 | panic!("parameterized-macro: the #[parameterized_macro(..)] attribute should not be present more than once!"); 94 | } 95 | 96 | let unrelated_attributes = unrelated_attributes.iter().map(|attr| attr.quoted()); 97 | 98 | let test_macro = if use_test_macro.is_empty() { 99 | quote::quote!(#[test]) 100 | } else { 101 | let meta = use_test_macro[0]; 102 | let meta = meta.quoted(); 103 | quote::quote!(#[#meta]) 104 | }; 105 | 106 | quote::quote! { 107 | #test_macro 108 | #(#unrelated_attributes)* 109 | #constness #asyncness #unsafety #visibility fn #identifier() #return_type { 110 | #(#bindings)* 111 | 112 | #body 113 | } 114 | } 115 | } 116 | 117 | fn generate_binding(identifier: &syn::Ident, ty: &syn::Type, expr: &syn::Expr) -> TokenStream { 118 | quote::quote! { 119 | let #identifier: #ty = #expr; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # parameterized 2 | 3 | Procedural macro based parameterized testing library. 4 | Useful, when you want to run a test case with many different input sets. 5 | 6 | When defining a parameterized test case, the `#[parameterized(...)]` attribute should be used instead of `#[test]`. 7 | 8 | This crate was inspired by JUnit `@ParameterizedTest`. 9 | 10 | If you consider using parameterized, you can also check out [Yare](https://github.com/foresterre/yare) which is a 11 | variation on `parameterized`, which pivots the parameters, so you can define your own identifier for cases. 12 | Alternatively, there is [Sif](https://github.com/foresterre/sif) where each case can be defined by a 13 | separate `#[case(...)`] attribute. 14 | 15 | ### Example: 16 | 17 | Additional examples can be found at the 18 | parameterized-examples repository, 19 | and in the tests folder. 20 | 21 | ```rust 22 | enum Fruit { 23 | Apple, 24 | Bramble(BrambleFruit), 25 | Pear, 26 | } 27 | 28 | trait NameOf { 29 | fn name_of(&self) -> &str; 30 | } 31 | 32 | impl NameOf for Fruit { 33 | fn name_of(&self) -> &str { 34 | match self { 35 | Fruit::Apple => "apple", 36 | Fruit::Bramble(fruit) => fruit.name_of(), 37 | Fruit::Pear => "pear", 38 | } 39 | } 40 | } 41 | 42 | enum BrambleFruit { 43 | Blackberry, 44 | } 45 | 46 | impl NameOf for BrambleFruit { 47 | fn name_of(&self) -> &str { 48 | match self { 49 | BrambleFruit::Blackberry => "blackberry", 50 | } 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use super::*; 57 | use parameterized::parameterized; 58 | 59 | 60 | #[parameterized(fruit = { 61 | Fruit::Apple, Fruit::Pear, Fruit::Bramble(BrambleFruit::Blackberry) 62 | }, name = { 63 | "apple", "pear", "blackberry" 64 | })] 65 | fn a_fruity_test(fruit: Fruit, name: &str) { 66 | assert_eq!(fruit.name_of(), name) 67 | } 68 | } 69 | ``` 70 | 71 | ### Custom test attributes (e.g. tokio::test) 72 | 73 | By default, the code generation step of the `parameterized` attribute will generate test cases marked with a `#[test]` 74 | attribute. 75 | For example, for the parameterized test case **add5** below, the following code would be generated: 76 | 77 | **Parameterized test case** 78 | 79 | ```rust 80 | use parameterized::parameterized; 81 | 82 | #[parameterized(input = { 83 | 0, 1 84 | }, expected = { 85 | 5, 6 86 | })] 87 | fn add5(input: u32, expected: u32) { 88 | assert_eq!(input + 5, expected); 89 | } 90 | ``` 91 | 92 | **Generated code** 93 | 94 | ```rust 95 | #[cfg(test)] 96 | mod add5 { 97 | use super::*; 98 | 99 | #[test] 100 | fn case_0() { 101 | assert_eq!(0 + 5, 5); 102 | } 103 | 104 | #[test] 105 | fn case_1() { 106 | assert_eq!(1 + 5, 6); 107 | } 108 | } 109 | ``` 110 | 111 | However, sometimes a different test macro is desired, for example with `#[tokio::test]`. 112 | To let `#[parameterized]` use a user specified test macro, you may add the `#[parameterized_macro(...)]` attribute after 113 | a `#[parameterized]` attribute. 114 | Since we use `#[tokio::test]` in this example, we also add the `async` item to the function signature (but this is of 115 | course not mandatory for other macros). 116 | 117 | **Parameterized test case with `#[parameterized_macro(...)]`** 118 | 119 | ```rust,ignore 120 | #[parameterized(input = { 121 | 0, 1 122 | }, expected = { 123 | 5, 6 124 | })] 125 | #[parameterized_macro(tokio::test)] 126 | async fn add5(input: u32, expected: u32) { 127 | assert_eq!(input + 5, expected); 128 | } 129 | ``` 130 | 131 | Gotchas: 132 | 133 | * The `#[parameterized_macro(...)]` must always be specified after a `#[parameterized(...)]` attribute 134 | * For now, only one `#[parameterized_macro(...)]` attribute per parameterized test function is supported. 135 | * While you can rename the parameterized attribute using import renaming ( 136 | e.g. `use parameterized::parameterized as pm`), 137 | the `parameterized_macro` attribute cannot be renamed, since it's not actually defined as a separate macro. 138 | Instead, the `parameterized` parses this attribute as well. 139 | 140 | ### Imports 141 | 142 | If you prefer not to import this library (with `use parameterized::parameterized;`) in every test module, you can put 143 | the following snippet at the top of your crate root: 144 | 145 | ```rust 146 | #[cfg(test)] 147 | #[macro_use] 148 | extern crate parameterized; 149 | ``` 150 | 151 |
152 | 153 | ### IDE 'run test' intent 154 | 155 | IntelliJ IDEA recognizes test cases and provides context menus which allow you to run tests within a certain scope 156 | (such as a module or a single test case). For example, in IntelliJ you can usually run individual test cases by clicking 157 | the ▶ icon in the gutter. Unfortunately, attribute macros are currently not expanded by `intellij-rust`. 158 | This means that the IDE will not recognize test cases generated as a result of attribute macros (such as the 159 | `parameterized` macro published by this crate). 160 | 161 | A workaround can be found below (if you have a better solution, please feel free to open an issue; thank you in 162 | advance!) 163 | 164 | ```rust 165 | fn squared(input: i8) -> i8 { 166 | input * input 167 | } 168 | 169 | #[cfg(test)] 170 | mod tests { 171 | use super::*; 172 | 173 | use parameterized::parameterized as pm; 174 | use parameterized::ide; 175 | 176 | mod squared_tests { // <-- 177 | use super::*; 178 | 179 | ide!(); // <-- 180 | 181 | #[pm(input = { 182 | -2, -1, 0, 1, 2 183 | }, expected = { 184 | 4, 1, 0, 1, 4 185 | })] 186 | fn test_squared(input: i8, output: i8) { 187 | assert_eq(squared(input), output); 188 | } 189 | } 190 | } 191 | ``` 192 | 193 | Here we created an empty test case (using the `ide!()` macro) which will mark the surrounding module as 'containing test 194 | cases'. In 195 | the gutter you will find the ▶ icon next to the module. This allows you to run test cases per module. 196 | 197 | Note: `intellij-rust` does expand declarative macro's (with the new macro engine which can be 198 | selected in the 'settings' menu), such as this `ide!` macro. 199 | 200 |
201 | 202 | ### License 203 | 204 | Licensed under either of Apache License, Version 205 | 2.0 or MIT license at your option. 206 | 207 | 208 | Unless you explicitly state otherwise, any contribution intentionally submitted 209 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall 210 | be dual licensed as above, without any additional terms or conditions. 211 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Cargo.lock.msrv: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bitflags" 7 | version = "2.10.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 10 | 11 | [[package]] 12 | name = "bytes" 13 | version = "1.10.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 16 | 17 | [[package]] 18 | name = "cfg-if" 19 | version = "1.0.4" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 22 | 23 | [[package]] 24 | name = "equivalent" 25 | version = "1.0.2" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 28 | 29 | [[package]] 30 | name = "fnv" 31 | version = "1.0.7" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 34 | 35 | [[package]] 36 | name = "glob" 37 | version = "0.3.3" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 40 | 41 | [[package]] 42 | name = "hashbrown" 43 | version = "0.16.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" 46 | 47 | [[package]] 48 | name = "indexmap" 49 | version = "2.11.4" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" 52 | dependencies = [ 53 | "equivalent", 54 | "hashbrown", 55 | ] 56 | 57 | [[package]] 58 | name = "itoa" 59 | version = "1.0.15" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 62 | 63 | [[package]] 64 | name = "libc" 65 | version = "0.2.177" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" 68 | 69 | [[package]] 70 | name = "lock_api" 71 | version = "0.4.14" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" 74 | dependencies = [ 75 | "scopeguard", 76 | ] 77 | 78 | [[package]] 79 | name = "memchr" 80 | version = "2.7.6" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 83 | 84 | [[package]] 85 | name = "mio" 86 | version = "1.1.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" 89 | dependencies = [ 90 | "libc", 91 | "wasi", 92 | "windows-sys 0.61.2", 93 | ] 94 | 95 | [[package]] 96 | name = "parameterized" 97 | version = "2.0.0" 98 | dependencies = [ 99 | "parameterized-macro", 100 | "tokio", 101 | ] 102 | 103 | [[package]] 104 | name = "parameterized-macro" 105 | version = "2.0.0" 106 | dependencies = [ 107 | "fnv", 108 | "indexmap", 109 | "proc-macro2", 110 | "quote", 111 | "syn", 112 | "trybuild", 113 | ] 114 | 115 | [[package]] 116 | name = "parking_lot" 117 | version = "0.12.5" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" 120 | dependencies = [ 121 | "lock_api", 122 | "parking_lot_core", 123 | ] 124 | 125 | [[package]] 126 | name = "parking_lot_core" 127 | version = "0.9.12" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" 130 | dependencies = [ 131 | "cfg-if", 132 | "libc", 133 | "redox_syscall", 134 | "smallvec", 135 | "windows-link", 136 | ] 137 | 138 | [[package]] 139 | name = "pin-project-lite" 140 | version = "0.2.16" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 143 | 144 | [[package]] 145 | name = "proc-macro2" 146 | version = "1.0.103" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" 149 | dependencies = [ 150 | "unicode-ident", 151 | ] 152 | 153 | [[package]] 154 | name = "quote" 155 | version = "1.0.41" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" 158 | dependencies = [ 159 | "proc-macro2", 160 | ] 161 | 162 | [[package]] 163 | name = "redox_syscall" 164 | version = "0.5.18" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 167 | dependencies = [ 168 | "bitflags", 169 | ] 170 | 171 | [[package]] 172 | name = "ryu" 173 | version = "1.0.20" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 176 | 177 | [[package]] 178 | name = "scopeguard" 179 | version = "1.2.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 182 | 183 | [[package]] 184 | name = "serde" 185 | version = "1.0.228" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 188 | dependencies = [ 189 | "serde_core", 190 | ] 191 | 192 | [[package]] 193 | name = "serde_core" 194 | version = "1.0.228" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 197 | dependencies = [ 198 | "serde_derive", 199 | ] 200 | 201 | [[package]] 202 | name = "serde_derive" 203 | version = "1.0.228" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 206 | dependencies = [ 207 | "proc-macro2", 208 | "quote", 209 | "syn", 210 | ] 211 | 212 | [[package]] 213 | name = "serde_json" 214 | version = "1.0.145" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 217 | dependencies = [ 218 | "itoa", 219 | "memchr", 220 | "ryu", 221 | "serde", 222 | "serde_core", 223 | ] 224 | 225 | [[package]] 226 | name = "serde_spanned" 227 | version = "1.0.3" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" 230 | dependencies = [ 231 | "serde_core", 232 | ] 233 | 234 | [[package]] 235 | name = "signal-hook-registry" 236 | version = "1.4.6" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" 239 | dependencies = [ 240 | "libc", 241 | ] 242 | 243 | [[package]] 244 | name = "smallvec" 245 | version = "1.15.1" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 248 | 249 | [[package]] 250 | name = "socket2" 251 | version = "0.6.1" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" 254 | dependencies = [ 255 | "libc", 256 | "windows-sys 0.60.2", 257 | ] 258 | 259 | [[package]] 260 | name = "syn" 261 | version = "2.0.108" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" 264 | dependencies = [ 265 | "proc-macro2", 266 | "quote", 267 | "unicode-ident", 268 | ] 269 | 270 | [[package]] 271 | name = "target-triple" 272 | version = "1.0.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b" 275 | 276 | [[package]] 277 | name = "termcolor" 278 | version = "1.4.1" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 281 | dependencies = [ 282 | "winapi-util", 283 | ] 284 | 285 | [[package]] 286 | name = "tokio" 287 | version = "1.48.0" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" 290 | dependencies = [ 291 | "bytes", 292 | "libc", 293 | "mio", 294 | "parking_lot", 295 | "pin-project-lite", 296 | "signal-hook-registry", 297 | "socket2", 298 | "tokio-macros", 299 | "windows-sys 0.61.2", 300 | ] 301 | 302 | [[package]] 303 | name = "tokio-macros" 304 | version = "2.6.0" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" 307 | dependencies = [ 308 | "proc-macro2", 309 | "quote", 310 | "syn", 311 | ] 312 | 313 | [[package]] 314 | name = "toml" 315 | version = "0.9.8" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" 318 | dependencies = [ 319 | "indexmap", 320 | "serde_core", 321 | "serde_spanned", 322 | "toml_datetime", 323 | "toml_parser", 324 | "toml_writer", 325 | "winnow", 326 | ] 327 | 328 | [[package]] 329 | name = "toml_datetime" 330 | version = "0.7.3" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" 333 | dependencies = [ 334 | "serde_core", 335 | ] 336 | 337 | [[package]] 338 | name = "toml_parser" 339 | version = "1.0.4" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" 342 | dependencies = [ 343 | "winnow", 344 | ] 345 | 346 | [[package]] 347 | name = "toml_writer" 348 | version = "1.0.4" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" 351 | 352 | [[package]] 353 | name = "trybuild" 354 | version = "1.0.113" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "559b6a626c0815c942ac98d434746138b4f89ddd6a1b8cbb168c6845fb3376c5" 357 | dependencies = [ 358 | "glob", 359 | "serde", 360 | "serde_derive", 361 | "serde_json", 362 | "target-triple", 363 | "termcolor", 364 | "toml", 365 | ] 366 | 367 | [[package]] 368 | name = "unicode-ident" 369 | version = "1.0.22" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 372 | 373 | [[package]] 374 | name = "wasi" 375 | version = "0.11.1+wasi-snapshot-preview1" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 378 | 379 | [[package]] 380 | name = "winapi-util" 381 | version = "0.1.11" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 384 | dependencies = [ 385 | "windows-sys 0.61.2", 386 | ] 387 | 388 | [[package]] 389 | name = "windows-link" 390 | version = "0.2.1" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 393 | 394 | [[package]] 395 | name = "windows-sys" 396 | version = "0.60.2" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 399 | dependencies = [ 400 | "windows-targets", 401 | ] 402 | 403 | [[package]] 404 | name = "windows-sys" 405 | version = "0.61.2" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 408 | dependencies = [ 409 | "windows-link", 410 | ] 411 | 412 | [[package]] 413 | name = "windows-targets" 414 | version = "0.53.5" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" 417 | dependencies = [ 418 | "windows-link", 419 | "windows_aarch64_gnullvm", 420 | "windows_aarch64_msvc", 421 | "windows_i686_gnu", 422 | "windows_i686_gnullvm", 423 | "windows_i686_msvc", 424 | "windows_x86_64_gnu", 425 | "windows_x86_64_gnullvm", 426 | "windows_x86_64_msvc", 427 | ] 428 | 429 | [[package]] 430 | name = "windows_aarch64_gnullvm" 431 | version = "0.53.1" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" 434 | 435 | [[package]] 436 | name = "windows_aarch64_msvc" 437 | version = "0.53.1" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" 440 | 441 | [[package]] 442 | name = "windows_i686_gnu" 443 | version = "0.53.1" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" 446 | 447 | [[package]] 448 | name = "windows_i686_gnullvm" 449 | version = "0.53.1" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" 452 | 453 | [[package]] 454 | name = "windows_i686_msvc" 455 | version = "0.53.1" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" 458 | 459 | [[package]] 460 | name = "windows_x86_64_gnu" 461 | version = "0.53.1" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" 464 | 465 | [[package]] 466 | name = "windows_x86_64_gnullvm" 467 | version = "0.53.1" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" 470 | 471 | [[package]] 472 | name = "windows_x86_64_msvc" 473 | version = "0.53.1" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" 476 | 477 | [[package]] 478 | name = "winnow" 479 | version = "0.7.13" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" 482 | --------------------------------------------------------------------------------