├── .github └── workflows │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── README.tpl ├── crates ├── test-case-core │ ├── Cargo.toml │ ├── LICENSE │ └── src │ │ ├── comment.rs │ │ ├── complex_expr.rs │ │ ├── expr.rs │ │ ├── lib.rs │ │ ├── modifier.rs │ │ ├── test_case.rs │ │ ├── test_matrix │ │ ├── matrix_product.rs │ │ └── mod.rs │ │ └── utils.rs └── test-case-macros │ ├── Cargo.toml │ ├── LICENSE │ └── src │ └── lib.rs ├── scripts ├── publish.sh └── test_all.sh ├── src └── lib.rs └── tests ├── acceptance_cases ├── allow_stays_on_fn │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── cases_can_be_declared_on_async_methods │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── cases_can_be_declared_on_non_test_items │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── main.rs ├── cases_can_be_ignored │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── cases_can_panic │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── cases_can_return_result │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── cases_can_use_regex │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── cases_support_basic_features │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── cases_support_complex_assertions │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── cases_support_generics │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── cases_support_keyword_using │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── cases_support_keyword_with │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── cases_support_multiple_calling_methods │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── cases_support_pattern_matching │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── features_produce_human_readable_errors │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── matrices_compilation_errors │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── matrices_support_basic_features │ ├── Cargo.toml │ └── src │ └── lib.rs ├── acceptance_tests.rs └── snapshots ├── rust-nightly ├── acceptance__allow_stays_on_fn.snap ├── acceptance__cases_can_be_declared_on_async_methods.snap ├── acceptance__cases_can_be_declared_on_non_test_items.snap ├── acceptance__cases_can_be_ignored.snap ├── acceptance__cases_can_panic.snap ├── acceptance__cases_can_return_result.snap ├── acceptance__cases_can_use_regex.snap ├── acceptance__cases_declared_on_non_test_items_can_be_used.snap ├── acceptance__cases_support_basic_features.snap ├── acceptance__cases_support_complex_assertions.snap ├── acceptance__cases_support_generics.snap ├── acceptance__cases_support_keyword_using.snap ├── acceptance__cases_support_keyword_with.snap ├── acceptance__cases_support_multiple_calling_methods.snap ├── acceptance__cases_support_pattern_matching.snap ├── acceptance__features_produce_human_readable_errors.snap ├── acceptance__matrices_compilation_errors.snap └── acceptance__matrices_support_basic_features.snap └── rust-stable ├── acceptance__allow_stays_on_fn.snap ├── acceptance__cases_can_be_declared_on_async_methods.snap ├── acceptance__cases_can_be_declared_on_non_test_items.snap ├── acceptance__cases_can_be_ignored.snap ├── acceptance__cases_can_panic.snap ├── acceptance__cases_can_return_result.snap ├── acceptance__cases_can_use_regex.snap ├── acceptance__cases_declared_on_non_test_items_can_be_used.snap ├── acceptance__cases_support_basic_features.snap ├── acceptance__cases_support_complex_assertions.snap ├── acceptance__cases_support_generics.snap ├── acceptance__cases_support_keyword_using.snap ├── acceptance__cases_support_keyword_with.snap ├── acceptance__cases_support_multiple_calling_methods.snap ├── acceptance__cases_support_pattern_matching.snap ├── acceptance__features_produce_human_readable_errors.snap ├── acceptance__matrices_compilation_errors.snap └── acceptance__matrices_support_basic_features.snap /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | - develop 9 | schedule: 10 | - cron: "0 0 * * *" 11 | 12 | jobs: 13 | validate: 14 | name: Validate clippy and rustfmt 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout source code 19 | uses: actions/checkout@v4 20 | 21 | - name: Install Rust 22 | uses: dtolnay/rust-toolchain@stable 23 | with: 24 | components: rustfmt, clippy 25 | 26 | - name: Cache 27 | uses: Swatinem/rust-cache@v2 28 | 29 | - name: Check 30 | run: cargo check --all-features --all-targets 31 | 32 | - name: Clippy 33 | run: cargo clippy --all-features --all-targets -- -D warnings 34 | 35 | - name: Fmt 36 | run: cargo fmt -- --check 37 | 38 | test: 39 | name: Test crate 40 | runs-on: ${{ matrix.os }} 41 | strategy: 42 | fail-fast: false 43 | matrix: 44 | os: 45 | - ubuntu-latest 46 | - windows-latest 47 | - macOS-latest 48 | toolchain: 49 | - nightly 50 | - stable 51 | 52 | steps: 53 | - name: Checkout source code 54 | uses: actions/checkout@v4 55 | 56 | - name: Install Rust 57 | uses: dtolnay/rust-toolchain@master 58 | with: 59 | toolchain: ${{ matrix.toolchain }} 60 | 61 | - name: Cache 62 | uses: Swatinem/rust-cache@v2 63 | 64 | - name: Test 65 | env: 66 | CARGO_TERM_COLOR: never 67 | SNAPSHOT_DIR: rust-${{ matrix.toolchain }} 68 | run: cargo test 69 | 70 | msrv-build: 71 | name: Build crate with documented MSRV 72 | runs-on: ubuntu-latest 73 | 74 | steps: 75 | - name: Checkout source code 76 | uses: actions/checkout@v4 77 | 78 | - name: Read crate metadata 79 | id: metadata 80 | run: echo "rust-version=$(sed -ne 's/rust-version *= *\"\(.*\)\"/\1/p' Cargo.toml)" >> $GITHUB_OUTPUT 81 | 82 | - name: Install Rust 83 | uses: dtolnay/rust-toolchain@master 84 | with: 85 | toolchain: ${{ steps.metadata.outputs.rust-version }} 86 | 87 | - name: Cache 88 | uses: Swatinem/rust-cache@v2 89 | 90 | - name: Build 91 | run: cargo build 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | .idea 13 | 14 | *.new 15 | 16 | # Ignore toolchain installed by script 17 | rust-toolchain 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 3.3.1 4 | ### Fixes 5 | * Avoid emitting additional misleading error messages by proc-macro2-diagnostics (#138) 6 | 7 | ## 3.3.0 8 | ### Features 9 | * Allow comments in `test-matrix` macro (#132) 10 | 11 | ### Improvements 12 | * Drop `proc-macro-error` dependency & improve error messages (#136) 13 | 14 | ## 3.2.1 15 | ### Improvements 16 | * Update `syn` dependency to 2.0 17 | * Ensure that `test-case` selects correct version of it's `core` and `macros` subcrates 18 | 19 | ## 3.2.0 20 | ### Features 21 | * Add `test_matrix` macro: generates test cases from Cartesian product of possible test function argument values (#128) 22 | 23 | ### Improvements 24 | * Retain `allow` attributes on test functions (#127) 25 | 26 | ## 3.1.0 27 | ### Features 28 | * Copy attribute span to generated test functions so that IDEs recognize them properly as individual tests 29 | 30 | ### Improvements 31 | * Added LICENSE file to child crates 32 | 33 | ## 3.0.0 34 | 35 | [IMPORTANT] Starting with 3.0 release we are changing `test-case` MSRV policy to support only 3 latest stable releases. 36 | 37 | ### Improvements 38 | * Split out functional parts of `test-case-macros` crate into `test-case-core` for easy reuse by external libraries 39 | 40 | ## 2.2.2 41 | ### Fixes 42 | * Use fully qualified `test` macro path to avoid conflicts in workspace (#105) 43 | 44 | ## 2.2.1 45 | ### Fixes 46 | * Ensure `test-case` depends on correct version of `test-case-macros` 47 | 48 | ## 2.2.0 49 | ### Features 50 | * Support `ignore["reason"]` syntax (#102) 51 | 52 | ## 2.1.0 53 | ### Features 54 | * Support `matches_regex` complex test-case (requires `with-regex` feature) (#98) 55 | * Support `len`, `count` and `empty` complex test-cases (#97) 56 | 57 | ### Fixes 58 | * Support keyword `ignore` on void fn (#100) 59 | 60 | ### Improvements 61 | * Move macros to separate subcrate so that test-case can export other things (#96) 62 | 63 | ## 2.0.2 64 | ### Fixes 65 | * Covered missing cases in `matches X if Y` *test_case* variant (fixes the fact that previous bug fix didn't produce guard code) 66 | 67 | 68 | ## 2.0.1 69 | ### Fixes 70 | * `matches Pattern if condition` parses correctly (`if condition` part wasn't allowed) 71 | 72 | ## 2.0.0 73 | ### Features 74 | * `=> with |x: T| assert!(x)` custom inline test assertions 75 | * `=> using path::to::fn` custom fn test assertions 76 | * `ignore|inconclusive` can be combined with other keywords (eg.: `=> ignore matches Ok(_)`) 77 | * `=> it|is ...` syntax is a built-in (_previously required `hamcrest2` crate integration_) 78 | * Tested items are left in place where they were defined #77 79 | * Simple test cases allow `Result<(), _>` return types similar to native `#[test]` macro 80 | 81 | ### Improvements 82 | * Significant code refactoring 83 | * Improved test case name selection 84 | 85 | ### Breaking changes 86 | * Deprecation of `inconclusive` within test description string - it will no longer act like modifier keyword 87 | * Deprecation of `hamcrest2` integration 88 | * Deprecation of `allow_result` feature 89 | 90 | ## V1.2.3 91 | ### Fixes 92 | * Fix regression where `panics` and `inconclusive` were not allowed on `test_cases` returning a value 93 | * Fix case where `test_case` would allow to return a type when only single attribute was used 94 | 95 | ## V1.2.2 96 | ### Fixes 97 | * `test-case` no longer allows returning values from tested function without `=>` pattern (thanks to @tarka) 98 | * Behaviour can be reenabled via `allow_result` feature 99 | 100 | ## V1.2.1 101 | ### Fixes 102 | * Disabled clippy warning when test-case was generating `assert_eq(bool, bool)` expression. 103 | 104 | ## V1.2.0 105 | ### Features 106 | * Allow usage of fully qualified attribute `#[test_case::test_case]` (thanks to @tomprince) 107 | 108 | ### Improvements 109 | * Stopped code from emmiting unneded `()` expression in test cases with `expected` fragment (thanks to @martinvonz) 110 | 111 | ## V1.1.0 112 | ### Features 113 | * Added support for using `hamcrest2` assertions with test case 114 | * Enabled support of `async` via tokio or similar 115 | * Enabled attribute passthrough for test cases - it means that you can combine `test-case` with other testing frameworks, 116 | given at least one `#[test_case]` attribute appears before mentioned framework in testing function 117 | 118 | ### Deprecation 119 | * `inconclusive` inside test case name will not be supported starting `2.0.0` 120 | 121 | ## V1.0.0 122 | ### Features 123 | * Added support for three new keywords: `panics`, `matches` and `inconclusive` which can be applied after `=>` token. 124 | 125 | `matches` gives possibility to test patterns, like: 126 | ```rust 127 | #[test_case("foo" => matches Some(("foo", _)))] 128 | ``` 129 | 130 | `panics` gives `should_panic(expected="...")` for one `test_case`: 131 | ```rust 132 | #[test_case(true => panics "Panic error message" ; "This should panic")] 133 | #[test_case(false => None ; "But this should return None")] 134 | ``` 135 | 136 | `inconclusive` ignores one specific test case.- thanks to @luke_biel 137 | ```rust 138 | #[test_case("42")] 139 | #[test_case("XX" ; "inconclusive - parsing letters temporarily doesn't work, but it's ok")] 140 | #[test_case("na" => inconclusive ())] 141 | ``` 142 | 143 | ### Major improvements 144 | * Added extra unit tests - thanks to @luke-biel 145 | * Replace `parented_test_case` with parsing `test_case` directly from args - thanks to @luke-biel 146 | * Added keeping trailing underscores in names - thanks to @rzumer 147 | ### Minor improvements 148 | * Moved `lazy-static` dependency to `dev-dependencies` 149 | * Fixed README - thanks to @luke-biel and @drwilco 150 | ### Upgraded dependencies 151 | * Upgraded `insta` to `0.12.0` 152 | 153 | ## v0.3.3 154 | ### Fixes 155 | * Fixed "inconclusive" feature with different cases. 156 | 157 | ## v0.3.2 158 | ### Fixes 159 | * Added support for `impl Trait` - it worked in v2.x crate. 160 | ### Minor improvements 161 | * Added extra test cases 162 | ### Upgraded dependencies 163 | * Upgraded `version_check` to `v0.9.1` 164 | 165 | ## v0.3.1 166 | ### Minor improvements: 167 | * Refreshed readme 168 | * Added CI for stable version of Rust. - thanks to @macisamuele 169 | * Limited crate to Rust 1.29+ - thanks to @macisamuele 170 | ### Upgraded dependencies: 171 | * Upgraded `syn`, `quote` and `proc-macro-2` to `v1` 172 | * Upgraded `lazy-static` to `1.4.0` 173 | * Upgraded `insta` to `0.11.0` 174 | 175 | ## v0.3.0 176 | ### Breaking changes 177 | * Crate has new maintainer: Wojciech Polak :hand: :tada: 178 | * Crate has new name, as `test-case-derive` had no meaning for `derive` part. 179 | * Delimiter for test case description is `;` instead of `::`. 180 | 181 | Reason: `::` is valid part of expression and rustc treats const variable as path 182 | 183 | ### New features 184 | * Proper error propagation :tada: 185 | When there is for example a typo in function body, rustc can now show location 186 | of it instead of `test_case` location. 187 | * Internally for tests crate uses `cargo insta` for snapshot testing 188 | * Attribute is now compatible all other attributes like `#[should_panic]` 189 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-case" 3 | version = "3.3.1" 4 | edition = "2021" 5 | authors = ["Marcin Sas-Szymanski ", "Wojciech Polak ", "Łukasz Biel "] 6 | description = "Provides #[test_case(...)] procedural macro attribute for generating parametrized test cases easily" 7 | keywords = ["test", "case", "tests", "unit", "testing"] 8 | categories = ["development-tools", "development-tools::testing"] 9 | readme = "README.md" 10 | license = "MIT" 11 | repository = "https://github.com/frondeus/test-case" 12 | documentation = "https://docs.rs/test-case" 13 | exclude = ["tests/snapshots/**/*"] 14 | rust-version = "1.63" 15 | 16 | [features] 17 | with-regex = ["regex", "test-case-macros/with-regex"] 18 | 19 | [badges] 20 | maintenance = { status = "actively-developed" } 21 | 22 | [lib] 23 | doctest = false 24 | path = "src/lib.rs" 25 | 26 | [dependencies] 27 | test-case-macros = { version = "3.2.1", path = "crates/test-case-macros", default-features = false } 28 | regex = { version = "1.5", optional = true } 29 | 30 | [dev-dependencies] 31 | insta = "1.12" 32 | itertools = "0.11" 33 | regex = "1.5" 34 | 35 | [workspace] 36 | members = ["crates/*"] 37 | 38 | [[test]] 39 | name = "acceptance" 40 | path = "tests/acceptance_tests.rs" 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Marcin Sas-Szymański 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Crates.io](https://img.shields.io/crates/v/test-case.svg)](https://crates.io/crates/test-case) 2 | [![Crates.io](https://img.shields.io/crates/d/test-case.svg)](https://crates.io/crates/test-case) 3 | [![Docs.rs](https://docs.rs/test-case/badge.svg)](https://docs.rs/test-case) 4 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/rust-lang/docs.rs/master/LICENSE) 5 | [![Build Status](https://github.com/frondeus/test-case/workflows/Test/badge.svg)](https://github.com/frondeus/test-case/actions) 6 | ![Maintenance](https://img.shields.io/badge/maintenance-activly--developed-brightgreen.svg) 7 | 8 | # Test Case 9 | 10 | ## Overview 11 | `test_case` crate provides procedural macro attribute that generates parametrized test instances. 12 | 13 | ## Getting Started 14 | 15 | Crate has to be added as a dependency to `Cargo.toml`: 16 | 17 | ```toml 18 | [dev-dependencies] 19 | test-case = "*" 20 | ``` 21 | 22 | and imported to the scope of a block where it's being called 23 | (since attribute name collides with rust's built-in `custom_test_frameworks`) via: 24 | 25 | ```rust 26 | use test_case::test_case; 27 | ``` 28 | 29 | ## Example usage: 30 | 31 | ```rust 32 | #[cfg(test)] 33 | mod tests { 34 | use test_case::test_case; 35 | 36 | #[test_case(-2, -4 ; "when both operands are negative")] 37 | #[test_case(2, 4 ; "when both operands are positive")] 38 | #[test_case(4, 2 ; "when operands are swapped")] 39 | fn multiplication_tests(x: i8, y: i8) { 40 | let actual = (x * y).abs(); 41 | 42 | assert_eq!(8, actual) 43 | } 44 | } 45 | ``` 46 | 47 | Output from `cargo test` for this example: 48 | 49 | ```sh 50 | $ cargo test 51 | 52 | running 4 tests 53 | test tests::multiplication_tests::when_both_operands_are_negative ... ok 54 | test tests::multiplication_tests::when_both_operands_are_positive ... ok 55 | test tests::multiplication_tests::when_operands_are_swapped ... ok 56 | 57 | test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 58 | ``` 59 | 60 | ### Test Matrix 61 | 62 | The `#[test_matrix(...)]` macro allows generating multiple test cases from the 63 | Cartesian product of one or more possible values for each test function argument. The 64 | number of arguments to the `test_matrix` macro must be the same as the number of arguments to 65 | the test function. Each macro argument can be: 66 | 67 | 1. A list in array (`[x, y, ...]`) or tuple (`(x, y, ...)`) syntax. The values can be any 68 | valid [expression](https://doc.rust-lang.org/reference/expressions.html). 69 | 2. A closed numeric range expression (e.g. `0..100` or `1..=99`), which will generate 70 | argument values for all integers in the range. 71 | 3. A single expression, which can be used to keep one argument constant while varying the 72 | other test function arguments using a list or range. 73 | 74 | #### Example usage: 75 | 76 | ```rust 77 | #[cfg(test)] 78 | mod tests { 79 | use test_case::test_matrix; 80 | 81 | #[test_matrix( 82 | [-2, 2], 83 | [-4, 4] 84 | )] 85 | fn multiplication_tests(x: i8, y: i8) { 86 | let actual = (x * y).abs(); 87 | 88 | assert_eq!(8, actual) 89 | } 90 | } 91 | ``` 92 | 93 | ## MSRV Policy 94 | 95 | Starting with version 3.0 and up `test-case` introduces policy of only supporting latest stable Rust. 96 | These changes may happen overnight, so if your stack is lagging behind current stable release, 97 | it may be best to consider locking `test-case` version with `=` in your `Cargo.toml`. 98 | 99 | ## Documentation 100 | 101 | Most up to date documentation is available in our [wiki](https://github.com/frondeus/test-case/wiki). 102 | 103 | # License 104 | 105 | Licensed under of MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) 106 | 107 | # Contributing 108 | 109 | Project roadmap is available at [link](https://github.com/frondeus/test-case/issues/74). All contributions are welcome. 110 | 111 | Recommended tools: 112 | * `cargo readme` - to regenerate README.md based on template and lib.rs comments 113 | * `cargo insta` - to review test snapshots 114 | * `cargo edit` - to add/remove dependencies 115 | * `cargo fmt` - to format code 116 | * `cargo clippy` - for all insights and tips 117 | * `cargo fix` - for fixing warnings 118 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | [![Crates.io](https://img.shields.io/crates/v/test-case.svg)](https://crates.io/crates/test-case) 2 | [![Crates.io](https://img.shields.io/crates/d/test-case.svg)](https://crates.io/crates/test-case) 3 | [![Docs.rs](https://docs.rs/test-case/badge.svg)](https://docs.rs/test-case) 4 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/rust-lang/docs.rs/master/LICENSE) 5 | [![Build Status](https://github.com/frondeus/test-case/workflows/Test/badge.svg)](https://github.com/frondeus/test-case/actions) 6 | {{badges}} 7 | 8 | # Test Case 9 | 10 | {{readme}} 11 | 12 | # License 13 | 14 | Licensed under of MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) 15 | 16 | # Contributing 17 | 18 | Project roadmap is available at [link](https://github.com/frondeus/test-case/issues/74). All contributions are welcome. 19 | 20 | Recommended tools: 21 | * `cargo readme` - to regenerate README.md based on template and lib.rs comments 22 | * `cargo insta` - to review test snapshots 23 | * `cargo edit` - to add/remove dependencies 24 | * `cargo fmt` - to format code 25 | * `cargo clippy` - for all insights and tips 26 | * `cargo fix` - for fixing warnings 27 | 28 | -------------------------------------------------------------------------------- /crates/test-case-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-case-core" 3 | version = "3.3.1" 4 | edition = "2021" 5 | authors = ["Marcin Sas-Szymanski ", "Wojciech Polak ", "Łukasz Biel "] 6 | description = "Provides core functionality for parsing #[test_case(...)] procedural macro attribute for generating parametrized test cases easily" 7 | keywords = ["test", "case", "tests", "unit", "testing"] 8 | categories = ["development-tools", "development-tools::testing"] 9 | readme = "../../README.md" 10 | license = "MIT" 11 | repository = "https://github.com/frondeus/test-case" 12 | documentation = "https://docs.rs/test-case" 13 | 14 | [features] 15 | with-regex = [] 16 | 17 | [badges] 18 | maintenance = { status = "actively-developed" } 19 | 20 | [lib] 21 | doctest = false 22 | path = "src/lib.rs" 23 | 24 | [dependencies] 25 | cfg-if = "1.0" 26 | proc-macro2 = { version = "1.0", features = [] } 27 | quote = "1.0" 28 | syn = { version = "2.0", features = ["full", "extra-traits"] } 29 | -------------------------------------------------------------------------------- /crates/test-case-core/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /crates/test-case-core/src/comment.rs: -------------------------------------------------------------------------------- 1 | use crate::TokenStream2; 2 | use quote::ToTokens; 3 | use syn::parse::{Parse, ParseStream}; 4 | use syn::{LitStr, Token}; 5 | 6 | #[derive(Clone, Debug)] 7 | pub struct TestCaseComment { 8 | _semicolon: Token![;], 9 | pub comment: LitStr, 10 | } 11 | 12 | impl Parse for TestCaseComment { 13 | fn parse(input: ParseStream) -> syn::Result { 14 | Ok(Self { 15 | _semicolon: input.parse()?, 16 | comment: input.parse()?, 17 | }) 18 | } 19 | } 20 | 21 | impl ToTokens for TestCaseComment { 22 | fn to_tokens(&self, tokens: &mut TokenStream2) { 23 | self._semicolon.to_tokens(tokens); 24 | self.comment.to_tokens(tokens); 25 | } 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use crate::comment::TestCaseComment; 31 | use proc_macro2::TokenStream; 32 | use syn::parse_quote; 33 | 34 | #[test] 35 | fn parses_token_stream() { 36 | let input: TokenStream = parse_quote! { ; "abcdef" }; 37 | let actual: TestCaseComment = syn::parse2(input).unwrap(); 38 | assert_eq!(actual.comment.value(), "abcdef") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /crates/test-case-core/src/complex_expr.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::fmt_syn; 2 | use proc_macro2::Group; 3 | use proc_macro2::Span; 4 | use proc_macro2::TokenStream; 5 | use quote::{quote, TokenStreamExt}; 6 | use std::fmt::{Display, Formatter}; 7 | use syn::parse::{Parse, ParseStream}; 8 | use syn::{parse_quote, Expr}; 9 | 10 | mod kw { 11 | syn::custom_keyword!(eq); 12 | syn::custom_keyword!(equal_to); 13 | syn::custom_keyword!(lt); 14 | syn::custom_keyword!(less_than); 15 | syn::custom_keyword!(gt); 16 | syn::custom_keyword!(greater_than); 17 | syn::custom_keyword!(leq); 18 | syn::custom_keyword!(less_or_equal_than); 19 | syn::custom_keyword!(geq); 20 | syn::custom_keyword!(greater_or_equal_than); 21 | syn::custom_keyword!(almost); 22 | syn::custom_keyword!(almost_equal_to); 23 | syn::custom_keyword!(precision); 24 | syn::custom_keyword!(existing_path); 25 | syn::custom_keyword!(directory); 26 | syn::custom_keyword!(dir); 27 | syn::custom_keyword!(file); 28 | syn::custom_keyword!(contains); 29 | syn::custom_keyword!(contains_in_order); 30 | syn::custom_keyword!(not); 31 | syn::custom_keyword!(and); 32 | syn::custom_keyword!(or); 33 | syn::custom_keyword!(len); 34 | syn::custom_keyword!(has_length); 35 | syn::custom_keyword!(count); 36 | syn::custom_keyword!(has_count); 37 | syn::custom_keyword!(empty); 38 | syn::custom_keyword!(matching_regex); 39 | syn::custom_keyword!(matches_regex); 40 | } 41 | 42 | #[derive(Clone, Debug, PartialEq, Eq)] 43 | pub enum OrderingToken { 44 | Eq, 45 | Lt, 46 | Gt, 47 | Leq, 48 | Geq, 49 | } 50 | 51 | #[derive(Clone, Debug, PartialEq, Eq)] 52 | pub enum PathToken { 53 | Any, 54 | Dir, 55 | File, 56 | } 57 | 58 | #[derive(Clone, Debug, PartialEq, Eq)] 59 | pub struct Ord { 60 | pub token: OrderingToken, 61 | pub expected_value: Box, 62 | } 63 | 64 | #[derive(Clone, Debug, PartialEq, Eq)] 65 | pub struct AlmostEqual { 66 | pub expected_value: Box, 67 | pub precision: Box, 68 | } 69 | 70 | #[derive(Clone, Debug, PartialEq, Eq)] 71 | pub struct Path { 72 | pub token: PathToken, 73 | } 74 | 75 | #[derive(Clone, Debug, PartialEq, Eq)] 76 | pub struct Contains { 77 | pub expected_element: Box, 78 | } 79 | 80 | #[derive(Clone, Debug, PartialEq, Eq)] 81 | pub struct ContainsInOrder { 82 | pub expected_slice: Box, 83 | } 84 | 85 | #[derive(Clone, Debug, PartialEq, Eq)] 86 | pub struct Len { 87 | pub expected_len: Box, 88 | } 89 | 90 | #[derive(Clone, Debug, PartialEq, Eq)] 91 | pub struct Count { 92 | pub expected_len: Box, 93 | } 94 | 95 | #[cfg(feature = "with-regex")] 96 | #[derive(Clone, Debug, PartialEq, Eq)] 97 | pub struct Regex { 98 | pub expected_regex: Box, 99 | } 100 | 101 | #[derive(Clone, Debug, PartialEq)] 102 | pub enum ComplexTestCase { 103 | Not(Box), 104 | And(Vec), 105 | Or(Vec), 106 | Ord(Ord), 107 | AlmostEqual(AlmostEqual), 108 | Path(Path), 109 | Contains(Contains), 110 | ContainsInOrder(ContainsInOrder), 111 | Len(Len), 112 | Count(Count), 113 | Empty, 114 | #[cfg(feature = "with-regex")] 115 | Regex(Regex), 116 | } 117 | 118 | impl Parse for ComplexTestCase { 119 | fn parse(input: ParseStream) -> syn::Result { 120 | let item = Self::parse_single_item(input)?; 121 | 122 | Ok(if input.peek(kw::and) { 123 | ComplexTestCase::And(parse_kw_repeat::(item, input)?) 124 | } else if input.peek(kw::or) { 125 | ComplexTestCase::Or(parse_kw_repeat::(item, input)?) 126 | } else { 127 | item 128 | }) 129 | } 130 | } 131 | 132 | impl Display for OrderingToken { 133 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 134 | match self { 135 | OrderingToken::Eq => f.write_str("eq"), 136 | OrderingToken::Lt => f.write_str("lt"), 137 | OrderingToken::Gt => f.write_str("gt"), 138 | OrderingToken::Leq => f.write_str("leq"), 139 | OrderingToken::Geq => f.write_str("geq"), 140 | } 141 | } 142 | } 143 | 144 | impl Display for PathToken { 145 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 146 | match self { 147 | PathToken::Any => f.write_str("path"), 148 | PathToken::Dir => f.write_str("dir"), 149 | PathToken::File => f.write_str("file"), 150 | } 151 | } 152 | } 153 | 154 | impl Display for ComplexTestCase { 155 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 156 | match self { 157 | ComplexTestCase::Not(not) => write!(f, "not {not}"), 158 | ComplexTestCase::And(cases) => { 159 | write!(f, "{}", cases[0])?; 160 | for case in cases[1..].iter() { 161 | write!(f, " and {case}")?; 162 | } 163 | Ok(()) 164 | } 165 | ComplexTestCase::Or(cases) => { 166 | write!(f, "{}", cases[0])?; 167 | for case in cases[1..].iter() { 168 | write!(f, " or {case}")?; 169 | } 170 | Ok(()) 171 | } 172 | ComplexTestCase::Ord(Ord { 173 | token, 174 | expected_value, 175 | }) => write!(f, "{} {}", token, fmt_syn(expected_value)), 176 | ComplexTestCase::AlmostEqual(AlmostEqual { 177 | expected_value, 178 | precision, 179 | }) => write!( 180 | f, 181 | "almost {} p {}", 182 | fmt_syn(expected_value), 183 | fmt_syn(precision) 184 | ), 185 | ComplexTestCase::Path(Path { token }) => write!(f, "path {token}"), 186 | ComplexTestCase::Contains(Contains { expected_element }) => { 187 | write!(f, "contains {}", fmt_syn(expected_element)) 188 | } 189 | ComplexTestCase::ContainsInOrder(ContainsInOrder { expected_slice }) => { 190 | write!(f, "contains in order {}", fmt_syn(expected_slice)) 191 | } 192 | ComplexTestCase::Len(Len { expected_len }) => { 193 | write!(f, "len {}", fmt_syn(expected_len)) 194 | } 195 | ComplexTestCase::Count(Count { expected_len }) => { 196 | write!(f, "count {}", fmt_syn(expected_len)) 197 | } 198 | ComplexTestCase::Empty => { 199 | write!(f, "empty") 200 | } 201 | #[cfg(feature = "with-regex")] 202 | ComplexTestCase::Regex(Regex { expected_regex }) => { 203 | write!(f, "regex {}", fmt_syn(expected_regex)) 204 | } 205 | } 206 | } 207 | } 208 | 209 | impl ComplexTestCase { 210 | pub fn assertion(&self) -> TokenStream { 211 | let tokens = self.boolean_check(); 212 | 213 | quote! { assert!(#tokens) } 214 | } 215 | 216 | fn boolean_check(&self) -> TokenStream { 217 | match self { 218 | ComplexTestCase::Not(not) => not_assertion(not), 219 | ComplexTestCase::And(cases) => and_assertion(cases), 220 | ComplexTestCase::Or(cases) => or_assertion(cases), 221 | ComplexTestCase::Ord(Ord { 222 | token, 223 | expected_value, 224 | }) => ord_assertion(token, expected_value), 225 | ComplexTestCase::AlmostEqual(AlmostEqual { 226 | expected_value, 227 | precision, 228 | }) => almost_equal_assertion(expected_value, precision), 229 | ComplexTestCase::Path(Path { token }) => path_assertion(token), 230 | ComplexTestCase::Contains(Contains { expected_element }) => { 231 | contains_assertion(expected_element) 232 | } 233 | ComplexTestCase::ContainsInOrder(ContainsInOrder { expected_slice }) => { 234 | contains_in_order_assertion(expected_slice) 235 | } 236 | ComplexTestCase::Len(Len { expected_len }) => len_assertion(expected_len), 237 | ComplexTestCase::Count(Count { expected_len }) => count_assertion(expected_len), 238 | ComplexTestCase::Empty => empty_assertion(), 239 | #[cfg(feature = "with-regex")] 240 | ComplexTestCase::Regex(Regex { expected_regex }) => regex_assertion(expected_regex), 241 | } 242 | } 243 | 244 | fn parse_single_item(input: ParseStream) -> syn::Result { 245 | Ok(if let Ok(group) = Group::parse(input) { 246 | syn::parse2(group.stream())? 247 | } else if input.parse::().is_ok() || input.parse::().is_ok() { 248 | ComplexTestCase::Ord(Ord { 249 | token: OrderingToken::Eq, 250 | expected_value: input.parse()?, 251 | }) 252 | } else if input.parse::().is_ok() || input.parse::().is_ok() { 253 | ComplexTestCase::Ord(Ord { 254 | token: OrderingToken::Lt, 255 | expected_value: input.parse()?, 256 | }) 257 | } else if input.parse::().is_ok() || input.parse::().is_ok() { 258 | ComplexTestCase::Ord(Ord { 259 | token: OrderingToken::Gt, 260 | expected_value: input.parse()?, 261 | }) 262 | } else if input.parse::().is_ok() 263 | || input.parse::().is_ok() 264 | { 265 | ComplexTestCase::Ord(Ord { 266 | token: OrderingToken::Leq, 267 | expected_value: input.parse()?, 268 | }) 269 | } else if input.parse::().is_ok() 270 | || input.parse::().is_ok() 271 | { 272 | ComplexTestCase::Ord(Ord { 273 | token: OrderingToken::Geq, 274 | expected_value: input.parse()?, 275 | }) 276 | } else if input.parse::().is_ok() 277 | || input.parse::().is_ok() 278 | { 279 | let target = input.parse()?; 280 | let _ = input.parse::()?; 281 | let precision = input.parse()?; 282 | ComplexTestCase::AlmostEqual(AlmostEqual { 283 | expected_value: target, 284 | precision, 285 | }) 286 | } else if input.parse::().is_ok() { 287 | ComplexTestCase::Path(Path { 288 | token: PathToken::Any, 289 | }) 290 | } else if input.parse::().is_ok() || input.parse::().is_ok() { 291 | ComplexTestCase::Path(Path { 292 | token: PathToken::Dir, 293 | }) 294 | } else if input.parse::().is_ok() { 295 | ComplexTestCase::Path(Path { 296 | token: PathToken::File, 297 | }) 298 | } else if input.parse::().is_ok() { 299 | ComplexTestCase::Contains(Contains { 300 | expected_element: input.parse()?, 301 | }) 302 | } else if input.parse::().is_ok() { 303 | ComplexTestCase::ContainsInOrder(ContainsInOrder { 304 | expected_slice: input.parse()?, 305 | }) 306 | } else if input.parse::().is_ok() { 307 | ComplexTestCase::Not(Box::new(input.parse()?)) 308 | } else if input.parse::().is_ok() || input.parse::().is_ok() { 309 | ComplexTestCase::Len(Len { 310 | expected_len: input.parse()?, 311 | }) 312 | } else if input.parse::().is_ok() || input.parse::().is_ok() { 313 | ComplexTestCase::Count(Count { 314 | expected_len: input.parse()?, 315 | }) 316 | } else if input.parse::().is_ok() { 317 | ComplexTestCase::Empty 318 | } else if input.parse::().is_ok() 319 | || input.parse::().is_ok() 320 | { 321 | cfg_if::cfg_if! { 322 | if #[cfg(feature = "with-regex")] { 323 | ComplexTestCase::Regex(Regex { 324 | expected_regex: input.parse()?, 325 | }) 326 | } else { 327 | return Err(input.error("'with-regex' feature is required to use 'matches-regex' keyword")); 328 | } 329 | } 330 | } else { 331 | return Err(input.error("cannot parse complex expression")); 332 | }) 333 | } 334 | } 335 | 336 | fn and_assertion(cases: &[ComplexTestCase]) -> TokenStream { 337 | let ts = cases[0].boolean_check(); 338 | let mut ts: TokenStream = parse_quote! { #ts }; 339 | 340 | for case in cases.iter().skip(1) { 341 | let case = case.boolean_check(); 342 | let case: TokenStream = parse_quote! { && #case }; 343 | ts.append_all(case); 344 | } 345 | 346 | ts 347 | } 348 | 349 | fn or_assertion(cases: &[ComplexTestCase]) -> TokenStream { 350 | let ts = cases[0].boolean_check(); 351 | let mut ts: TokenStream = parse_quote! { #ts }; 352 | 353 | for case in cases.iter().skip(1) { 354 | let case = case.boolean_check(); 355 | let case: TokenStream = parse_quote! { || #case }; 356 | ts.append_all(case); 357 | } 358 | 359 | ts 360 | } 361 | 362 | fn parse_kw_repeat( 363 | first: ComplexTestCase, 364 | input: ParseStream, 365 | ) -> syn::Result> { 366 | let mut acc = vec![first]; 367 | while input.parse::().is_ok() { 368 | acc.push(ComplexTestCase::parse_single_item(input)?); 369 | } 370 | Ok(acc) 371 | } 372 | 373 | fn negate(tokens: TokenStream) -> TokenStream { 374 | quote! { 375 | !{#tokens} 376 | } 377 | } 378 | 379 | fn contains_in_order_assertion(expected_slice: &Expr) -> TokenStream { 380 | parse_quote! { 381 | { 382 | let mut _tc_outcome = false; 383 | for i in 0..=_result.len() - #expected_slice.len() { 384 | if #expected_slice == _result[i..i+#expected_slice.len()] { 385 | _tc_outcome = true; 386 | } 387 | } 388 | _tc_outcome 389 | } 390 | } 391 | } 392 | 393 | fn contains_assertion(expected_element: &Expr) -> TokenStream { 394 | parse_quote! { _result.iter().find(|i| i.eq(&&#expected_element)).is_some() } 395 | } 396 | 397 | fn path_assertion(token: &PathToken) -> TokenStream { 398 | match token { 399 | PathToken::Any => parse_quote! { std::path::Path::new(&_result).exists() }, 400 | PathToken::Dir => parse_quote! { std::path::Path::new(&_result).is_dir() }, 401 | PathToken::File => parse_quote! { std::path::Path::new(&_result).is_file() }, 402 | } 403 | } 404 | 405 | fn almost_equal_assertion(expected_value: &Expr, precision: &Expr) -> TokenStream { 406 | quote! { (_result - #expected_value).abs() < #precision } 407 | } 408 | 409 | fn ord_assertion(token: &OrderingToken, expected_value: &Expr) -> TokenStream { 410 | let ts: TokenStream = match token { 411 | OrderingToken::Eq => parse_quote! { == }, 412 | OrderingToken::Lt => parse_quote! { < }, 413 | OrderingToken::Gt => parse_quote! { > }, 414 | OrderingToken::Leq => parse_quote! { <= }, 415 | OrderingToken::Geq => parse_quote! { >= }, 416 | }; 417 | 418 | quote! { 419 | _result #ts #expected_value 420 | } 421 | } 422 | 423 | fn len_assertion(expected_len: &Expr) -> TokenStream { 424 | quote! { 425 | _result.len() == #expected_len 426 | } 427 | } 428 | 429 | fn count_assertion(expected_len: &Expr) -> TokenStream { 430 | quote! { 431 | std::iter::IntoIterator::into_iter(_result).count() == #expected_len 432 | } 433 | } 434 | 435 | fn empty_assertion() -> TokenStream { 436 | quote! { 437 | _result.is_empty() 438 | } 439 | } 440 | 441 | #[cfg(feature = "with-regex")] 442 | fn regex_assertion(expected_regex: &Expr) -> TokenStream { 443 | quote! { 444 | { 445 | let re = ::test_case::Regex::new(#expected_regex).expect("Regex::new"); 446 | re.is_match(_result) 447 | } 448 | } 449 | } 450 | 451 | fn not_assertion(not: &ComplexTestCase) -> TokenStream { 452 | match not { 453 | ComplexTestCase::Not(_) => { 454 | let msg = "multiple negations on single item are forbidden"; 455 | syn::Error::new(Span::call_site(), msg).into_compile_error() 456 | } 457 | ComplexTestCase::And(cases) => negate(and_assertion(cases)), 458 | ComplexTestCase::Or(cases) => negate(or_assertion(cases)), 459 | ComplexTestCase::Ord(Ord { 460 | token, 461 | expected_value, 462 | }) => negate(ord_assertion(token, expected_value)), 463 | ComplexTestCase::AlmostEqual(AlmostEqual { 464 | expected_value, 465 | precision, 466 | }) => negate(almost_equal_assertion(expected_value, precision)), 467 | ComplexTestCase::Path(Path { token }) => negate(path_assertion(token)), 468 | ComplexTestCase::Contains(Contains { expected_element }) => { 469 | negate(contains_assertion(expected_element)) 470 | } 471 | ComplexTestCase::ContainsInOrder(ContainsInOrder { expected_slice }) => { 472 | negate(contains_in_order_assertion(expected_slice)) 473 | } 474 | ComplexTestCase::Len(Len { expected_len }) => negate(len_assertion(expected_len)), 475 | ComplexTestCase::Count(Count { expected_len }) => negate(count_assertion(expected_len)), 476 | ComplexTestCase::Empty => negate(empty_assertion()), 477 | #[cfg(feature = "with-regex")] 478 | ComplexTestCase::Regex(Regex { expected_regex }) => negate(regex_assertion(expected_regex)), 479 | } 480 | } 481 | 482 | #[cfg(test)] 483 | mod tests { 484 | use crate::complex_expr::{ 485 | AlmostEqual, ComplexTestCase, Contains, ContainsInOrder, Count, Len, OrderingToken, Path, 486 | PathToken, 487 | }; 488 | use syn::{parse_quote, LitFloat, LitInt, LitStr}; 489 | 490 | macro_rules! assert_ord { 491 | ($actual:tt, $token:path, $value:tt) => { 492 | if let ComplexTestCase::Ord(ord) = $actual { 493 | assert_eq!(ord.token, $token); 494 | let lit = ord.expected_value; 495 | let actual_expr: LitFloat = parse_quote! { #lit }; 496 | assert_eq!(actual_expr.base10_parse::().unwrap(), $value) 497 | } else { 498 | panic!("invalid enum variant") 499 | } 500 | }; 501 | } 502 | 503 | macro_rules! assert_almost_eq { 504 | ($actual:tt, $value:tt, $precision:tt) => { 505 | if let ComplexTestCase::AlmostEqual(AlmostEqual { 506 | expected_value, 507 | precision, 508 | }) = $actual 509 | { 510 | let expected_value: LitFloat = parse_quote! { #expected_value }; 511 | assert_eq!(expected_value.base10_parse::().unwrap(), $value); 512 | let precision: LitFloat = parse_quote! { #precision }; 513 | assert_eq!(precision.base10_parse::().unwrap(), $precision); 514 | } else { 515 | panic!("invalid enum variant") 516 | } 517 | }; 518 | } 519 | 520 | #[test] 521 | #[allow(clippy::float_cmp)] 522 | fn parses_ord_token_stream() { 523 | let actual: ComplexTestCase = parse_quote! { equal_to 1.0 }; 524 | assert_ord!(actual, OrderingToken::Eq, 1.0); 525 | let actual: ComplexTestCase = parse_quote! { eq 0.0 }; 526 | assert_ord!(actual, OrderingToken::Eq, 0.0); 527 | 528 | let actual: ComplexTestCase = parse_quote! { less_than 2.0 }; 529 | assert_ord!(actual, OrderingToken::Lt, 2.0); 530 | let actual: ComplexTestCase = parse_quote! { lt 2.0 }; 531 | assert_ord!(actual, OrderingToken::Lt, 2.0); 532 | 533 | let actual: ComplexTestCase = parse_quote! { greater_than 2.0 }; 534 | assert_ord!(actual, OrderingToken::Gt, 2.0); 535 | let actual: ComplexTestCase = parse_quote! { gt 2.0 }; 536 | assert_ord!(actual, OrderingToken::Gt, 2.0); 537 | 538 | let actual: ComplexTestCase = parse_quote! { less_or_equal_than 2.0 }; 539 | assert_ord!(actual, OrderingToken::Leq, 2.0); 540 | let actual: ComplexTestCase = parse_quote! { leq 2.0 }; 541 | assert_ord!(actual, OrderingToken::Leq, 2.0); 542 | 543 | let actual: ComplexTestCase = parse_quote! { greater_or_equal_than 2.0 }; 544 | assert_ord!(actual, OrderingToken::Geq, 2.0); 545 | let actual: ComplexTestCase = parse_quote! { geq 2.0 }; 546 | assert_ord!(actual, OrderingToken::Geq, 2.0); 547 | } 548 | 549 | #[test] 550 | fn can_parse_eq_other_types() { 551 | let actual: ComplexTestCase = parse_quote! { equal_to "abcde" }; 552 | if let ComplexTestCase::Ord(ord) = actual { 553 | assert_eq!(ord.token, OrderingToken::Eq); 554 | let lit = ord.expected_value; 555 | let actual_expr: LitStr = parse_quote! { #lit }; 556 | assert_eq!(actual_expr.value(), "abcde") 557 | } else { 558 | panic!("invalid enum variant") 559 | } 560 | 561 | let actual: ComplexTestCase = parse_quote! { equal_to 1 }; 562 | if let ComplexTestCase::Ord(ord) = actual { 563 | assert_eq!(ord.token, OrderingToken::Eq); 564 | let lit = ord.expected_value; 565 | let actual_expr: LitInt = parse_quote! { #lit }; 566 | assert_eq!(actual_expr.base10_parse::().unwrap(), 1) 567 | } else { 568 | panic!("invalid enum variant") 569 | } 570 | } 571 | 572 | #[test] 573 | #[allow(clippy::float_cmp)] 574 | fn parses_almost_equal_token_stream() { 575 | let actual: ComplexTestCase = parse_quote! { almost_equal_to 1.0 precision 0.1 }; 576 | assert_almost_eq!(actual, 1.0, 0.1); 577 | let actual: ComplexTestCase = parse_quote! { almost_equal_to 1.0 precision 0.0f32 }; 578 | assert_almost_eq!(actual, 1.0, 0.0); 579 | } 580 | 581 | #[test] 582 | fn parses_path_token_stream() { 583 | let actual: ComplexTestCase = parse_quote! { existing_path }; 584 | assert_eq!( 585 | actual, 586 | ComplexTestCase::Path(Path { 587 | token: PathToken::Any 588 | }) 589 | ); 590 | let actual: ComplexTestCase = parse_quote! { file }; 591 | assert_eq!( 592 | actual, 593 | ComplexTestCase::Path(Path { 594 | token: PathToken::File 595 | }) 596 | ); 597 | let actual: ComplexTestCase = parse_quote! { dir }; 598 | assert_eq!( 599 | actual, 600 | ComplexTestCase::Path(Path { 601 | token: PathToken::Dir 602 | }) 603 | ); 604 | let actual: ComplexTestCase = parse_quote! { directory }; 605 | assert_eq!( 606 | actual, 607 | ComplexTestCase::Path(Path { 608 | token: PathToken::Dir 609 | }) 610 | ); 611 | } 612 | 613 | #[test] 614 | fn parses_contains_token_stream() { 615 | let actual: ComplexTestCase = parse_quote! { contains 1.0 }; 616 | assert_eq!( 617 | actual, 618 | ComplexTestCase::Contains(Contains { 619 | expected_element: Box::new(parse_quote! { 1.0 }) 620 | }) 621 | ); 622 | let actual: ComplexTestCase = parse_quote! { contains "abcde" }; 623 | assert_eq!( 624 | actual, 625 | ComplexTestCase::Contains(Contains { 626 | expected_element: Box::new(parse_quote! { "abcde" }) 627 | }) 628 | ); 629 | let actual: ComplexTestCase = parse_quote! { contains true }; 630 | assert_eq!( 631 | actual, 632 | ComplexTestCase::Contains(Contains { 633 | expected_element: Box::new(parse_quote! { true }) 634 | }) 635 | ); 636 | } 637 | 638 | #[test] 639 | fn parses_contains_in_order_token_stream() { 640 | let actual: ComplexTestCase = parse_quote! { contains_in_order [1, 2, 3] }; 641 | assert_eq!( 642 | actual, 643 | ComplexTestCase::ContainsInOrder(ContainsInOrder { 644 | expected_slice: Box::new(parse_quote! { [1, 2, 3] }) 645 | }) 646 | ) 647 | } 648 | 649 | #[test] 650 | fn parses_len_token_stream() { 651 | let actual1: ComplexTestCase = parse_quote! { len 10 }; 652 | let actual2: ComplexTestCase = parse_quote! { has_length 11 }; 653 | assert_eq!( 654 | actual1, 655 | ComplexTestCase::Len(Len { 656 | expected_len: Box::new(parse_quote! { 10 }) 657 | }) 658 | ); 659 | 660 | assert_eq!( 661 | actual2, 662 | ComplexTestCase::Len(Len { 663 | expected_len: Box::new(parse_quote! { 11 }) 664 | }) 665 | ) 666 | } 667 | 668 | #[test] 669 | fn parses_count_token_stream() { 670 | let actual1: ComplexTestCase = parse_quote! { count 10 }; 671 | let actual2: ComplexTestCase = parse_quote! { has_count 11 }; 672 | assert_eq!( 673 | actual1, 674 | ComplexTestCase::Count(Count { 675 | expected_len: Box::new(parse_quote! { 10 }) 676 | }) 677 | ); 678 | assert_eq!( 679 | actual2, 680 | ComplexTestCase::Count(Count { 681 | expected_len: Box::new(parse_quote! { 11 }) 682 | }) 683 | ) 684 | } 685 | 686 | #[test] 687 | fn parses_empty() { 688 | let actual: ComplexTestCase = parse_quote! { empty }; 689 | assert_eq!(actual, ComplexTestCase::Empty,) 690 | } 691 | 692 | #[test] 693 | fn parses_negation() { 694 | let actual: ComplexTestCase = parse_quote! { not eq 1.0 }; 695 | match actual { 696 | ComplexTestCase::Not(_) => {} 697 | _ => panic!("test failed"), 698 | }; 699 | } 700 | 701 | #[test] 702 | #[allow(clippy::float_cmp)] 703 | fn parses_grouping() { 704 | let actual: ComplexTestCase = parse_quote! { (lt 1.0) }; 705 | assert_ord!(actual, OrderingToken::Lt, 1.0); 706 | let actual: ComplexTestCase = parse_quote! { (((lt 1.0))) }; 707 | assert_ord!(actual, OrderingToken::Lt, 1.0); 708 | let actual: ComplexTestCase = parse_quote! { ({[(lt 1.0)]}) }; 709 | assert_ord!(actual, OrderingToken::Lt, 1.0) 710 | } 711 | 712 | #[test] 713 | fn parses_logic() { 714 | let actual: ComplexTestCase = parse_quote! { lt 1.0 and gt 0.0 }; 715 | match actual { 716 | ComplexTestCase::And(v) if v.len() == 2 => {} 717 | _ => panic!("test failed"), 718 | } 719 | let actual: ComplexTestCase = parse_quote! { lt 0.0 or gt 1.0 }; 720 | match actual { 721 | ComplexTestCase::Or(v) if v.len() == 2 => {} 722 | _ => panic!("test failed"), 723 | } 724 | let actual: ComplexTestCase = parse_quote! { lt 1.0 and gt 0.0 and eq 0.5 }; 725 | match actual { 726 | ComplexTestCase::And(v) if v.len() == 3 => {} 727 | _ => panic!("test failed"), 728 | } 729 | let actual: ComplexTestCase = parse_quote! { lt 0.0 or gt 1.0 or eq 2.0 }; 730 | match actual { 731 | ComplexTestCase::Or(v) if v.len() == 3 => {} 732 | _ => panic!("test failed"), 733 | } 734 | let actual: ComplexTestCase = parse_quote! { (lt 0.0 or gt 1.0) and eq 2.0 }; 735 | match actual { 736 | ComplexTestCase::And(v) if v.len() == 2 => {} 737 | _ => panic!("test failed"), 738 | } 739 | let actual: ComplexTestCase = parse_quote! { (lt 1.0 and gt 0.0) or eq 2.0 }; 740 | match actual { 741 | ComplexTestCase::Or(v) if v.len() == 2 => {} 742 | _ => panic!("test failed"), 743 | } 744 | } 745 | } 746 | -------------------------------------------------------------------------------- /crates/test-case-core/src/expr.rs: -------------------------------------------------------------------------------- 1 | use crate::complex_expr::ComplexTestCase; 2 | use crate::modifier::{parse_kws, Modifier}; 3 | use crate::utils::fmt_syn; 4 | use crate::TokenStream2; 5 | use quote::ToTokens; 6 | use std::collections::HashSet; 7 | use std::fmt::{Display, Formatter}; 8 | use syn::parse::{Parse, ParseStream}; 9 | use syn::token::If; 10 | use syn::{parse_quote, Attribute, Expr, Pat, Token}; 11 | 12 | pub mod kw { 13 | syn::custom_keyword!(matches); 14 | syn::custom_keyword!(using); 15 | syn::custom_keyword!(with); 16 | syn::custom_keyword!(it); 17 | syn::custom_keyword!(is); 18 | syn::custom_keyword!(panics); 19 | } 20 | 21 | #[derive(Clone, Debug)] 22 | pub struct TestCaseExpression { 23 | _token: Token![=>], 24 | pub extra_keywords: HashSet, 25 | pub result: TestCaseResult, 26 | } 27 | 28 | #[derive(Clone, Debug)] 29 | pub enum TestCaseResult { 30 | // test_case(a, b, c => keywords) 31 | Empty, 32 | // test_case(a, b, c => result) 33 | Simple(Expr), 34 | // test_case(a, b, c => matches Ok(_) if true) 35 | Matching(Pat, Option>), 36 | // test_case(a, b, c => panics "abcd") 37 | Panicking(Option), 38 | // test_case(a, b, c => with |v: T| assert!(v.is_nan())) 39 | With(Expr), 40 | // test_case(a, b, c => using assert_nan) 41 | UseFn(Expr), 42 | // test_case(a, b, c => is close to 4 precision 0.1) 43 | Complex(ComplexTestCase), 44 | } 45 | 46 | impl Parse for TestCaseExpression { 47 | fn parse(input: ParseStream) -> syn::Result { 48 | let token: Token![=>] = input.parse()?; 49 | let extra_keywords = parse_kws(input); 50 | 51 | if input.parse::().is_ok() { 52 | let pattern = Pat::parse_single(input)?; 53 | let guard = if input.peek(If) { 54 | let _if_kw: If = input.parse()?; 55 | let guard: Box = input.parse()?; 56 | Some(guard) 57 | } else { 58 | None 59 | }; 60 | 61 | Ok(TestCaseExpression { 62 | _token: token, 63 | extra_keywords, 64 | result: TestCaseResult::Matching(pattern, guard), 65 | }) 66 | } else if input.parse::().is_ok() || input.parse::().is_ok() { 67 | parse_with_keyword::<_, _>(input, token, extra_keywords, TestCaseResult::Complex) 68 | } else if input.parse::().is_ok() { 69 | parse_with_keyword::<_, _>(input, token, extra_keywords, TestCaseResult::UseFn) 70 | } else if input.parse::().is_ok() { 71 | parse_with_keyword::<_, _>(input, token, extra_keywords, TestCaseResult::With) 72 | } else if input.parse::().is_ok() { 73 | parse_with_keyword_ok::<_, _>(input, token, extra_keywords, TestCaseResult::Panicking) 74 | } else { 75 | let result = match input.parse::() { 76 | Ok(expr) => TestCaseResult::Simple(expr), 77 | Err(_) if !extra_keywords.is_empty() => TestCaseResult::Empty, 78 | Err(e) => return Err(e), 79 | }; 80 | 81 | Ok(Self { 82 | _token: token, 83 | extra_keywords, 84 | result, 85 | }) 86 | } 87 | } 88 | } 89 | 90 | impl Display for TestCaseExpression { 91 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 92 | for kw in &self.extra_keywords { 93 | write!(f, "{kw:?}")?; 94 | } 95 | write!(f, "{}", self.result) 96 | } 97 | } 98 | 99 | impl Display for TestCaseResult { 100 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 101 | match self { 102 | TestCaseResult::Simple(expr) => write!(f, "{}", fmt_syn(expr)), 103 | TestCaseResult::Matching(pat, expr) => { 104 | write!(f, "matching {} {}", fmt_syn(pat), fmt_syn(expr)) 105 | } 106 | TestCaseResult::Panicking(expr) => write!( 107 | f, 108 | "panicking {:?}", 109 | expr.as_ref().map(|inner| fmt_syn(&inner)) 110 | ), 111 | TestCaseResult::With(expr) => write!(f, "with {}", fmt_syn(expr)), 112 | TestCaseResult::UseFn(expr) => write!(f, "use {}", fmt_syn(expr)), 113 | TestCaseResult::Complex(complex) => write!(f, "complex {complex}"), 114 | TestCaseResult::Empty => write!(f, "empty"), 115 | } 116 | } 117 | } 118 | 119 | impl TestCaseExpression { 120 | pub fn assertion(&self) -> TokenStream2 { 121 | match &self.result { 122 | TestCaseResult::Simple(expr) => parse_quote! { assert_eq!(_result, #expr) }, 123 | TestCaseResult::Matching(pat, guard) => { 124 | let pat_str = pat.to_token_stream().to_string(); 125 | 126 | if let Some(guard) = guard { 127 | let guard_str = guard.to_token_stream().to_string(); 128 | 129 | parse_quote! { 130 | match _result { 131 | #pat if #guard => (), 132 | e => panic!("Expected `{} if {}` found {:?}", #pat_str, #guard_str, e) 133 | } 134 | } 135 | } else { 136 | parse_quote! { 137 | match _result { 138 | #pat => (), 139 | e => panic!("Expected `{}` found {:?}", #pat_str, e) 140 | } 141 | } 142 | } 143 | } 144 | TestCaseResult::Panicking(_) => TokenStream2::new(), 145 | TestCaseResult::With(expr) => parse_quote! { let fun = #expr; fun(_result) }, 146 | TestCaseResult::UseFn(path) => parse_quote! { #path(_result) }, 147 | TestCaseResult::Complex(complex) => complex.assertion(), 148 | TestCaseResult::Empty => TokenStream2::new(), 149 | } 150 | } 151 | 152 | pub fn attributes(&self) -> Vec { 153 | let mut attrs: Vec = self 154 | .extra_keywords 155 | .iter() 156 | .map(|modifier| modifier.attribute()) 157 | .collect(); 158 | if let TestCaseResult::Panicking(opt) = &self.result { 159 | if let Some(expr) = opt { 160 | attrs.push(parse_quote! { #[should_panic(expected = #expr)] }) 161 | } else { 162 | attrs.push(parse_quote! { #[should_panic] }) 163 | } 164 | } 165 | attrs 166 | } 167 | } 168 | 169 | fn parse_with_keyword( 170 | input: ParseStream, 171 | token: Token![=>], 172 | extra_keywords: HashSet, 173 | mapping: Mapping, 174 | ) -> syn::Result 175 | where 176 | Mapping: FnOnce(Inner) -> TestCaseResult, 177 | Inner: Parse, 178 | { 179 | Ok(TestCaseExpression { 180 | _token: token, 181 | extra_keywords, 182 | result: mapping(input.parse()?), 183 | }) 184 | } 185 | 186 | fn parse_with_keyword_ok( 187 | input: ParseStream, 188 | token: Token![=>], 189 | extra_keywords: HashSet, 190 | mapping: Mapping, 191 | ) -> syn::Result 192 | where 193 | Mapping: FnOnce(Option) -> TestCaseResult, 194 | Inner: Parse, 195 | { 196 | let result = (!input.is_empty()).then(|| input.parse()).transpose()?; 197 | Ok(TestCaseExpression { 198 | _token: token, 199 | extra_keywords, 200 | result: mapping(result), 201 | }) 202 | } 203 | -------------------------------------------------------------------------------- /crates/test-case-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream as TokenStream2; 2 | 3 | mod comment; 4 | mod complex_expr; 5 | mod expr; 6 | mod modifier; 7 | mod test_case; 8 | mod test_matrix; 9 | mod utils; 10 | 11 | pub use test_case::TestCase; 12 | pub use test_matrix::TestMatrix; 13 | -------------------------------------------------------------------------------- /crates/test-case-core/src/modifier.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::fmt::{Debug, Formatter}; 3 | use syn::parse::{Parse, ParseStream}; 4 | use syn::token::Bracket; 5 | use syn::{bracketed, parse_quote, Attribute, LitStr}; 6 | 7 | mod kw { 8 | syn::custom_keyword!(inconclusive); 9 | syn::custom_keyword!(ignore); 10 | } 11 | 12 | #[derive(Clone, PartialEq, Eq, Hash)] 13 | pub enum Modifier { 14 | Inconclusive, 15 | InconclusiveWithReason(LitStr), 16 | } 17 | 18 | impl Debug for Modifier { 19 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 20 | match self { 21 | Modifier::Inconclusive | Modifier::InconclusiveWithReason(_) => { 22 | write!(f, "inconclusive") 23 | } 24 | } 25 | } 26 | } 27 | 28 | impl Parse for Modifier { 29 | fn parse(input: ParseStream) -> syn::Result { 30 | if input.peek(kw::inconclusive) { 31 | let _: kw::inconclusive = input.parse()?; 32 | Self::parse_inconclusive(input) 33 | } else if input.peek(kw::ignore) { 34 | let _: kw::ignore = input.parse()?; 35 | Self::parse_inconclusive(input) 36 | } else { 37 | Err(syn::Error::new(input.span(), "unknown modifier keyword")) 38 | } 39 | } 40 | } 41 | 42 | impl Modifier { 43 | pub fn parse_inconclusive(input: ParseStream) -> syn::Result { 44 | if input.peek(Bracket) { 45 | let content; 46 | let _: Bracket = bracketed!(content in input); 47 | let reason: LitStr = content.parse()?; 48 | Ok(Self::InconclusiveWithReason(reason)) 49 | } else { 50 | Ok(Self::Inconclusive) 51 | } 52 | } 53 | 54 | pub fn attribute(&self) -> Attribute { 55 | match self { 56 | Modifier::Inconclusive => parse_quote! { #[ignore] }, 57 | Modifier::InconclusiveWithReason(r) => parse_quote! { #[ignore = #r] }, 58 | } 59 | } 60 | } 61 | 62 | pub fn parse_kws(input: ParseStream) -> HashSet { 63 | let mut kws = HashSet::new(); 64 | while let Ok(kw) = Modifier::parse(input) { 65 | kws.insert(kw); 66 | } 67 | kws 68 | } 69 | -------------------------------------------------------------------------------- /crates/test-case-core/src/test_case.rs: -------------------------------------------------------------------------------- 1 | use crate::comment::TestCaseComment; 2 | use crate::expr::{TestCaseExpression, TestCaseResult}; 3 | use crate::utils::fmt_syn; 4 | use proc_macro2::{Span as Span2, TokenStream as TokenStream2}; 5 | use quote::quote; 6 | use syn::parse::{Parse, ParseStream}; 7 | use syn::punctuated::Punctuated; 8 | use syn::{parse_quote, Error, Expr, Ident, ItemFn, ReturnType, Token}; 9 | 10 | #[derive(Debug)] 11 | pub struct TestCase { 12 | args: Punctuated, 13 | expression: Option, 14 | name: Ident, 15 | } 16 | 17 | impl Parse for TestCase { 18 | fn parse(input: ParseStream) -> Result { 19 | let args = Punctuated::parse_separated_nonempty_with(input, Expr::parse)?; 20 | let expression = (!input.is_empty()).then(|| input.parse()).transpose(); 21 | let comment = (!input.is_empty()).then(|| input.parse()).transpose(); 22 | // if both are errors, pick the expression error since it is more likely to be informative. 23 | // 24 | // TODO(https://github.com/frondeus/test-case/issues/135): avoid Result::ok entirely. 25 | let (expression, comment) = match (expression, comment) { 26 | (Err(expression), Err(_comment)) => return Err(expression), 27 | (expression, comment) => (expression.ok().flatten(), comment.ok().flatten()), 28 | }; 29 | 30 | Ok(Self::new_from_parsed(args, expression, comment)) 31 | } 32 | } 33 | impl TestCase { 34 | pub(crate) fn new>( 35 | args: I, 36 | expression: Option, 37 | comment: Option, 38 | ) -> Self { 39 | Self::new_from_parsed(args.into_iter().collect(), expression, comment) 40 | } 41 | 42 | pub(crate) fn new_from_parsed( 43 | args: Punctuated, 44 | expression: Option, 45 | comment: Option, 46 | ) -> Self { 47 | let name = Self::test_case_name_ident(args.iter(), expression.as_ref(), comment.as_ref()); 48 | 49 | Self { 50 | args, 51 | expression, 52 | name, 53 | } 54 | } 55 | 56 | pub(crate) fn new_with_prefixed_name>( 57 | args: I, 58 | expression: Option, 59 | prefix: &str, 60 | ) -> Self { 61 | let parsed_args = args.into_iter().collect::>(); 62 | let name = Self::prefixed_test_case_name(parsed_args.iter(), expression.as_ref(), prefix); 63 | 64 | Self { 65 | args: parsed_args, 66 | expression, 67 | name, 68 | } 69 | } 70 | 71 | pub fn test_case_name(&self) -> Ident { 72 | // The clone is kind of annoying here, but because this is behind a reference, we must clone 73 | // to preserve the signature without a breaking change 74 | // TODO: return a reference? 75 | self.name.clone() 76 | } 77 | 78 | pub fn render(&self, mut item: ItemFn, origin_span: Span2) -> TokenStream2 { 79 | let item_name = item.sig.ident.clone(); 80 | let arg_values = self.args.iter(); 81 | let test_case_name = { 82 | let mut test_case_name = self.test_case_name(); 83 | test_case_name.set_span(origin_span); 84 | test_case_name 85 | }; 86 | 87 | let mut attrs = self 88 | .expression 89 | .as_ref() 90 | .map(|expr| expr.attributes()) 91 | .unwrap_or_default(); 92 | 93 | attrs.push(parse_quote! { #[allow(clippy::bool_assert_comparison)] }); 94 | attrs.append(&mut item.attrs); 95 | 96 | let (mut signature, body) = if item.sig.asyncness.is_some() { 97 | ( 98 | quote! { async }, 99 | quote! { let _result = super::#item_name(#(#arg_values),*).await; }, 100 | ) 101 | } else { 102 | attrs.insert(0, parse_quote! { #[::core::prelude::v1::test] }); 103 | ( 104 | TokenStream2::new(), 105 | quote! { let _result = super::#item_name(#(#arg_values),*); }, 106 | ) 107 | }; 108 | 109 | let expected = if let Some(expr) = self.expression.as_ref() { 110 | attrs.extend(expr.attributes()); 111 | 112 | signature.extend(quote! { fn #test_case_name() }); 113 | 114 | if let TestCaseResult::Panicking(_) = expr.result { 115 | TokenStream2::new() 116 | } else { 117 | expr.assertion() 118 | } 119 | } else { 120 | signature.extend(if let ReturnType::Type(_, typ) = item.sig.output { 121 | quote! { fn #test_case_name() -> #typ } 122 | } else { 123 | quote! { fn #test_case_name() } 124 | }); 125 | 126 | quote! { _result } 127 | }; 128 | 129 | quote! { 130 | #(#attrs)* 131 | #signature { 132 | #body 133 | #expected 134 | } 135 | } 136 | } 137 | 138 | fn test_case_name_ident<'a, I: Iterator>( 139 | args: I, 140 | expression: Option<&TestCaseExpression>, 141 | comment: Option<&TestCaseComment>, 142 | ) -> Ident { 143 | let desc = Self::test_case_name_string(args, expression, comment); 144 | 145 | crate::utils::escape_test_name(desc) 146 | } 147 | 148 | fn prefixed_test_case_name<'a, I: Iterator>( 149 | args: I, 150 | expression: Option<&TestCaseExpression>, 151 | prefix: &str, 152 | ) -> Ident { 153 | let generated_name = Self::test_case_name_string(args, expression, None); 154 | let full_desc = format!("{prefix}_{generated_name}"); 155 | 156 | crate::utils::escape_test_name(full_desc) 157 | } 158 | 159 | fn test_case_name_string<'a, I: Iterator>( 160 | args: I, 161 | expression: Option<&TestCaseExpression>, 162 | comment: Option<&TestCaseComment>, 163 | ) -> String { 164 | comment 165 | .as_ref() 166 | .map(|item| item.comment.value()) 167 | .unwrap_or_else(|| { 168 | let mut acc = String::new(); 169 | for arg in args { 170 | acc.push_str(&fmt_syn(&arg)); 171 | acc.push('_'); 172 | } 173 | acc.push_str("expects"); 174 | if let Some(expression) = expression { 175 | acc.push(' '); 176 | acc.push_str(&expression.to_string()) 177 | } 178 | acc 179 | }) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /crates/test-case-core/src/test_matrix/matrix_product.rs: -------------------------------------------------------------------------------- 1 | //! Copied with minor modifications from itertools v0.11.0 2 | //! under MIT License 3 | //! 4 | //! Modifications called out in "(MOD)" comments, below 5 | //! 6 | //! Source file and commit hash: 7 | //! https://github.com/rust-itertools/itertools/blob/v0.11.0/src/adaptors/multi_product.rs 8 | //! ed6fbda086a913a787450a642acfd4d36dc07c3b 9 | 10 | #[derive(Clone)] 11 | /// An iterator adaptor that iterates over the cartesian product of 12 | /// multiple iterators of type `I`. 13 | /// 14 | /// An iterator element type is `Vec`. 15 | /// 16 | /// See [`.multi_cartesian_product()`](crate::Itertools::multi_cartesian_product) 17 | /// for more information. 18 | #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] 19 | pub struct MultiProduct(Vec>) 20 | where 21 | I: Iterator + Clone, 22 | I::Item: Clone; 23 | 24 | // (MOD) Omit `impl Debug for MultiProduct` 25 | // Not needed here and relies on a macro from itertools 26 | 27 | /// Create a new cartesian product iterator over an arbitrary number 28 | /// of iterators of the same type. 29 | /// 30 | /// Iterator element is of type `Vec`. 31 | pub fn multi_cartesian_product(iters: H) -> MultiProduct<::IntoIter> 32 | where 33 | H: Iterator, 34 | H::Item: IntoIterator, 35 | ::IntoIter: Clone, 36 | ::Item: Clone, 37 | { 38 | MultiProduct( 39 | iters 40 | .map(|i| MultiProductIter::new(i.into_iter())) 41 | .collect(), 42 | ) 43 | } 44 | 45 | #[derive(Clone, Debug)] 46 | /// Holds the state of a single iterator within a `MultiProduct`. 47 | struct MultiProductIter 48 | where 49 | I: Iterator + Clone, 50 | I::Item: Clone, 51 | { 52 | cur: Option, 53 | iter: I, 54 | iter_orig: I, 55 | } 56 | 57 | /// Holds the current state during an iteration of a `MultiProduct`. 58 | #[derive(Debug)] 59 | enum MultiProductIterState { 60 | StartOfIter, 61 | MidIter { on_first_iter: bool }, 62 | } 63 | 64 | impl MultiProduct 65 | where 66 | I: Iterator + Clone, 67 | I::Item: Clone, 68 | { 69 | /// Iterates the rightmost iterator, then recursively iterates iterators 70 | /// to the left if necessary. 71 | /// 72 | /// Returns true if the iteration succeeded, else false. 73 | fn iterate_last( 74 | multi_iters: &mut [MultiProductIter], 75 | mut state: MultiProductIterState, 76 | ) -> bool { 77 | use self::MultiProductIterState::*; 78 | 79 | if let Some((last, rest)) = multi_iters.split_last_mut() { 80 | let on_first_iter = match state { 81 | StartOfIter => { 82 | let on_first_iter = !last.in_progress(); 83 | state = MidIter { on_first_iter }; 84 | on_first_iter 85 | } 86 | MidIter { on_first_iter } => on_first_iter, 87 | }; 88 | 89 | if !on_first_iter { 90 | last.iterate(); 91 | } 92 | 93 | if last.in_progress() { 94 | true 95 | } else if MultiProduct::iterate_last(rest, state) { 96 | last.reset(); 97 | last.iterate(); 98 | // If iterator is None twice consecutively, then iterator is 99 | // empty; whole product is empty. 100 | last.in_progress() 101 | } else { 102 | false 103 | } 104 | } else { 105 | // Reached end of iterator list. On initialisation, return true. 106 | // At end of iteration (final iterator finishes), finish. 107 | match state { 108 | StartOfIter => false, 109 | MidIter { on_first_iter } => on_first_iter, 110 | } 111 | } 112 | } 113 | 114 | /// Returns the unwrapped value of the next iteration. 115 | fn curr_iterator(&self) -> Vec { 116 | self.0 117 | .iter() 118 | .map(|multi_iter| multi_iter.cur.clone().unwrap()) 119 | .collect() 120 | } 121 | 122 | /// Returns true if iteration has started and has not yet finished; false 123 | /// otherwise. 124 | fn in_progress(&self) -> bool { 125 | if let Some(last) = self.0.last() { 126 | last.in_progress() 127 | } else { 128 | false 129 | } 130 | } 131 | } 132 | 133 | impl MultiProductIter 134 | where 135 | I: Iterator + Clone, 136 | I::Item: Clone, 137 | { 138 | fn new(iter: I) -> Self { 139 | MultiProductIter { 140 | cur: None, 141 | iter: iter.clone(), 142 | iter_orig: iter, 143 | } 144 | } 145 | 146 | /// Iterate the managed iterator. 147 | fn iterate(&mut self) { 148 | self.cur = self.iter.next(); 149 | } 150 | 151 | /// Reset the managed iterator. 152 | fn reset(&mut self) { 153 | self.iter = self.iter_orig.clone(); 154 | } 155 | 156 | /// Returns true if the current iterator has been started and has not yet 157 | /// finished; false otherwise. 158 | fn in_progress(&self) -> bool { 159 | self.cur.is_some() 160 | } 161 | } 162 | 163 | impl Iterator for MultiProduct 164 | where 165 | I: Iterator + Clone, 166 | I::Item: Clone, 167 | { 168 | type Item = Vec; 169 | 170 | fn next(&mut self) -> Option { 171 | if MultiProduct::iterate_last(&mut self.0, MultiProductIterState::StartOfIter) { 172 | Some(self.curr_iterator()) 173 | } else { 174 | None 175 | } 176 | } 177 | 178 | fn count(self) -> usize { 179 | if self.0.is_empty() { 180 | return 0; 181 | } 182 | 183 | if !self.in_progress() { 184 | return self 185 | .0 186 | .into_iter() 187 | .fold(1, |acc, multi_iter| acc * multi_iter.iter.count()); 188 | } 189 | 190 | self.0.into_iter().fold( 191 | 0, 192 | |acc, 193 | MultiProductIter { 194 | iter, 195 | iter_orig, 196 | cur: _, 197 | }| { 198 | let total_count = iter_orig.count(); 199 | let cur_count = iter.count(); 200 | acc * total_count + cur_count 201 | }, 202 | ) 203 | } 204 | 205 | fn size_hint(&self) -> (usize, Option) { 206 | // Not ExactSizeIterator because size may be larger than usize 207 | if self.0.is_empty() { 208 | return (0, Some(0)); 209 | } 210 | 211 | if !self.in_progress() { 212 | return self.0.iter().fold((1, Some(1)), |acc, multi_iter| { 213 | size_hint::mul(acc, multi_iter.iter.size_hint()) 214 | }); 215 | } 216 | 217 | // (MOD) Clippy warning about unnecessary `ref` destructuring of `MultiProductIter` 218 | // Removed redundant `&` and `ref` 219 | self.0.iter().fold( 220 | (0, Some(0)), 221 | |acc, 222 | MultiProductIter { 223 | iter, 224 | iter_orig, 225 | cur: _, 226 | }| { 227 | let cur_size = iter.size_hint(); 228 | let total_size = iter_orig.size_hint(); 229 | size_hint::add(size_hint::mul(acc, total_size), cur_size) 230 | }, 231 | ) 232 | } 233 | 234 | fn last(self) -> Option { 235 | let iter_count = self.0.len(); 236 | 237 | // (MOD) Replaced `itertools::Itertools::while_some()` 238 | // `std::iter::Iterator::filter_map()` does the same 239 | // thing in this case, and doesn't require the `Itertools` trait 240 | let lasts: Self::Item = self 241 | .0 242 | .into_iter() 243 | .filter_map(|multi_iter| multi_iter.iter.last()) 244 | .collect(); 245 | 246 | if lasts.len() == iter_count { 247 | Some(lasts) 248 | } else { 249 | None 250 | } 251 | } 252 | } 253 | 254 | // (MOD) Copied two required functions and type alias 255 | // From itertools::size_hint module 256 | mod size_hint { 257 | /// `SizeHint` is the return type of `Iterator::size_hint()`. 258 | pub type SizeHint = (usize, Option); 259 | 260 | /// Add `SizeHint` correctly. 261 | #[inline] 262 | pub fn add(a: SizeHint, b: SizeHint) -> SizeHint { 263 | let min = a.0.saturating_add(b.0); 264 | let max = match (a.1, b.1) { 265 | (Some(x), Some(y)) => x.checked_add(y), 266 | _ => None, 267 | }; 268 | 269 | (min, max) 270 | } 271 | 272 | /// Multiply `SizeHint` correctly 273 | #[inline] 274 | pub fn mul(a: SizeHint, b: SizeHint) -> SizeHint { 275 | let low = a.0.saturating_mul(b.0); 276 | let hi = match (a.1, b.1) { 277 | (Some(x), Some(y)) => x.checked_mul(y), 278 | (Some(0), None) | (None, Some(0)) => Some(0), 279 | _ => None, 280 | }; 281 | (low, hi) 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /crates/test-case-core/src/test_matrix/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{iter, mem}; 2 | 3 | use proc_macro2::{Literal, Span}; 4 | use syn::{ 5 | parse::{Parse, ParseStream}, 6 | punctuated::Punctuated, 7 | spanned::Spanned, 8 | Expr, ExprLit, ExprRange, Lit, RangeLimits, Token, 9 | }; 10 | 11 | use crate::{comment::TestCaseComment, expr::TestCaseExpression, TestCase}; 12 | 13 | mod matrix_product; 14 | 15 | #[derive(Debug, Default)] 16 | pub struct TestMatrix { 17 | variables: Vec>, 18 | expression: Option, 19 | comment: Option, 20 | } 21 | 22 | impl TestMatrix { 23 | pub fn push_argument(&mut self, values: Vec) { 24 | self.variables.push(values); 25 | } 26 | 27 | pub fn cases(&self) -> impl Iterator { 28 | let expression = self.expression.clone(); 29 | let comment = self.comment.clone(); 30 | 31 | matrix_product::multi_cartesian_product(self.variables.iter().cloned()).map(move |v| { 32 | if let Some(comment) = comment.clone() { 33 | TestCase::new_with_prefixed_name( 34 | v, 35 | expression.clone(), 36 | comment.comment.value().as_ref(), 37 | ) 38 | } else { 39 | TestCase::new(v, expression.clone(), None) 40 | } 41 | }) 42 | } 43 | } 44 | 45 | impl Parse for TestMatrix { 46 | fn parse(input: ParseStream) -> syn::Result { 47 | let args: Punctuated = Punctuated::parse_separated_nonempty(input)?; 48 | 49 | let expression = (!input.is_empty()).then(|| input.parse()).transpose(); 50 | let comment = (!input.is_empty()).then(|| input.parse()).transpose(); 51 | // if both are errors, pick the expression error since it is more likely to be informative. 52 | // 53 | // TODO(https://github.com/frondeus/test-case/issues/135): avoid Result::ok entirely. 54 | let (expression, comment) = match (expression, comment) { 55 | (Err(expression), Err(_comment)) => return Err(expression), 56 | (expression, comment) => (expression.ok().flatten(), comment.ok().flatten()), 57 | }; 58 | 59 | let mut matrix = TestMatrix { 60 | expression, 61 | comment, 62 | ..Default::default() 63 | }; 64 | 65 | for arg in args { 66 | let values: Vec = match &arg { 67 | Expr::Array(v) => v.elems.iter().cloned().collect(), 68 | Expr::Tuple(v) => v.elems.iter().cloned().collect(), 69 | Expr::Range(ExprRange { 70 | start, limits, end, .. 71 | }) => { 72 | let start = isize_from_range_expr(limits.span(), start.as_deref())?; 73 | let end = isize_from_range_expr(limits.span(), end.as_deref())?; 74 | let range: Box> = match limits { 75 | RangeLimits::HalfOpen(_) => Box::from(start..end), 76 | RangeLimits::Closed(_) => Box::from(start..=end), 77 | }; 78 | range 79 | .map(|n| { 80 | let mut lit = Lit::new(Literal::isize_unsuffixed(n)); 81 | lit.set_span(arg.span()); 82 | Expr::from(ExprLit { lit, attrs: vec![] }) 83 | }) 84 | .collect() 85 | } 86 | v => iter::once(v.clone()).collect(), 87 | }; 88 | 89 | let mut value_literal_type = None; 90 | for expr in &values { 91 | if let Expr::Lit(ExprLit { lit, .. }) = expr { 92 | let first_literal_type = 93 | *value_literal_type.get_or_insert_with(|| mem::discriminant(lit)); 94 | if first_literal_type != mem::discriminant(lit) { 95 | return Err(syn::Error::new( 96 | lit.span(), 97 | "All literal values must be of the same type", 98 | )); 99 | } 100 | } 101 | } 102 | matrix.push_argument(values); 103 | } 104 | 105 | Ok(matrix) 106 | } 107 | } 108 | 109 | fn isize_from_range_expr(limits_span: Span, expr: Option<&Expr>) -> syn::Result { 110 | match expr { 111 | Some(Expr::Lit(ExprLit { 112 | lit: Lit::Int(n), .. 113 | })) => n.base10_parse(), 114 | Some(e) => Err(syn::Error::new( 115 | e.span(), 116 | "Range bounds can only be an integer literal", 117 | )), 118 | None => Err(syn::Error::new( 119 | limits_span, 120 | "Unbounded ranges are not supported", 121 | )), 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /crates/test-case-core/src/utils.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, Span}; 2 | use quote::ToTokens; 3 | 4 | pub fn escape_test_name(input: impl AsRef) -> Ident { 5 | if input.as_ref().is_empty() { 6 | return Ident::new("_empty", Span::call_site()); 7 | } 8 | 9 | let mut last_under = false; 10 | let mut ident: String = input 11 | .as_ref() 12 | .to_ascii_lowercase() 13 | .chars() 14 | .filter_map(|c| match c { 15 | c if c.is_alphanumeric() => { 16 | last_under = false; 17 | Some(c.to_ascii_lowercase()) 18 | } 19 | _ if !last_under => { 20 | last_under = true; 21 | Some('_') 22 | } 23 | _ => None, 24 | }) 25 | .collect(); 26 | 27 | if !ident.starts_with(|c: char| c == '_' || c.is_ascii_alphabetic()) { 28 | ident = format!("_{ident}"); 29 | } 30 | 31 | Ident::new(&ident, Span::call_site()) 32 | } 33 | 34 | pub fn fmt_syn(syn: &(impl ToTokens + Clone)) -> String { 35 | syn.clone().into_token_stream().to_string() 36 | } 37 | 38 | #[cfg(test)] 39 | mod tests { 40 | use super::*; 41 | 42 | mod escape_test_name { 43 | use super::*; 44 | 45 | #[test] 46 | fn converts_arbitrary_test_names() { 47 | assert_eq!( 48 | escape_test_name("word"), 49 | Ident::new("word", Span::call_site()) 50 | ); 51 | assert_eq!( 52 | escape_test_name("a simple sentence"), 53 | Ident::new("a_simple_sentence", Span::call_site()) 54 | ); 55 | assert_eq!( 56 | escape_test_name("extra spaces inbetween"), 57 | Ident::new("extra_spaces_inbetween", Span::call_site()) 58 | ); 59 | assert_eq!( 60 | escape_test_name(" extra end and start spaces "), 61 | Ident::new("_extra_end_and_start_spaces_", Span::call_site()) 62 | ); 63 | assert_eq!( 64 | escape_test_name("abcdefghijklmnoqprstuwvxyz1234567890"), 65 | Ident::new("abcdefghijklmnoqprstuwvxyz1234567890", Span::call_site()) 66 | ); 67 | } 68 | 69 | #[test] 70 | fn converts_to_lowercase() { 71 | assert_eq!( 72 | escape_test_name("ALL UPPER"), 73 | Ident::new("all_upper", Span::call_site()) 74 | ); 75 | assert_eq!( 76 | escape_test_name("MiXeD CaSe"), 77 | Ident::new("mixed_case", Span::call_site()) 78 | ); 79 | } 80 | 81 | #[test] 82 | fn handles_numeric_first_char() { 83 | assert_eq!( 84 | escape_test_name("1test"), 85 | Ident::new("_1test", Span::call_site()) 86 | ); 87 | } 88 | 89 | #[test] 90 | fn omits_unicode() { 91 | assert_eq!( 92 | escape_test_name("from⟶to"), 93 | Ident::new("from_to", Span::call_site()) 94 | ); 95 | } 96 | 97 | #[test] 98 | fn handles_empty_input() { 99 | assert_eq!( 100 | escape_test_name(""), 101 | Ident::new("_empty", Span::call_site()) 102 | ); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /crates/test-case-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-case-macros" 3 | version = "3.3.1" 4 | edition = "2021" 5 | authors = ["Marcin Sas-Szymanski ", "Wojciech Polak ", "Łukasz Biel "] 6 | description = "Provides #[test_case(...)] procedural macro attribute for generating parametrized test cases easily" 7 | keywords = ["test", "case", "tests", "unit", "testing"] 8 | categories = ["development-tools", "development-tools::testing"] 9 | readme = "../../README.md" 10 | license = "MIT" 11 | repository = "https://github.com/frondeus/test-case" 12 | documentation = "https://docs.rs/test-case" 13 | 14 | [features] 15 | with-regex = ["test-case-core/with-regex"] 16 | 17 | [badges] 18 | maintenance = { status = "actively-developed" } 19 | 20 | [lib] 21 | doctest = false 22 | proc-macro = true 23 | path = "src/lib.rs" 24 | 25 | [dependencies] 26 | proc-macro2 = { version = "1.0", features = [] } 27 | quote = "1.0" 28 | syn = { version = "2.0", features = ["full", "extra-traits", "parsing"] } 29 | test-case-core = { version = "3.2.1", path = "../test-case-core", default-features = false } 30 | -------------------------------------------------------------------------------- /crates/test-case-macros/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /crates/test-case-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | use proc_macro::TokenStream; 4 | 5 | use proc_macro2::Span as Span2; 6 | use syn::{parse_macro_input, ItemFn, Path}; 7 | 8 | use quote::quote; 9 | use syn::parse_quote; 10 | use syn::spanned::Spanned; 11 | use test_case_core::{TestCase, TestMatrix}; 12 | 13 | /// Generates tests for given set of data 14 | /// 15 | /// In general, test case consists of four elements: 16 | /// 17 | /// 1. _(Required)_ Arguments passed to test body 18 | /// 2. _(Optional)_ Expected result 19 | /// 3. _(Optional)_ Test case description 20 | /// 4. _(Required)_ Test body 21 | /// 22 | /// When _expected result_ is provided, it is compared against the actual value generated with _test body_ using `assert_eq!`. 23 | /// _Test cases_ that don't provide _expected result_ should contain custom assertions within _test body_ or return `Result` similar to `#[test]` macro. 24 | #[proc_macro_attribute] 25 | pub fn test_case(args: TokenStream, input: TokenStream) -> TokenStream { 26 | let test_case = parse_macro_input!(args as TestCase); 27 | let mut item = parse_macro_input!(input as ItemFn); 28 | 29 | let mut test_cases = vec![(test_case, Span2::call_site())]; 30 | 31 | match expand_additional_test_case_macros(&mut item) { 32 | Ok(cases) => test_cases.extend(cases), 33 | Err(err) => return err.into_compile_error().into(), 34 | } 35 | 36 | render_test_cases(&test_cases, item) 37 | } 38 | 39 | /// Generates tests for the cartesian product of a given set of data 40 | /// 41 | /// A test matrix consists of four elements: 42 | /// 43 | /// 1. _(Required)_ Sets of values to combine; the number of sets must be the same as the number of 44 | /// arguments to the test body function 45 | /// 2. _(Optional)_ Expected result (for all combinations of values) 46 | /// 3. _(Optional)_ Test case description (applied as a prefix the generated name of the test) 47 | /// 4. _(Required)_ Test body 48 | /// 49 | /// _Expected result_ and _Test body_ are the same as they are for the singular `#[test_case(...)]` 50 | /// macro but are applied to every case generated by `#[test_matrix(...)]`. 51 | #[proc_macro_attribute] 52 | pub fn test_matrix(args: TokenStream, input: TokenStream) -> TokenStream { 53 | let matrix = parse_macro_input!(args as TestMatrix); 54 | let mut item = parse_macro_input!(input as ItemFn); 55 | 56 | let mut test_cases = expand_test_matrix(&matrix, Span2::call_site()); 57 | 58 | match expand_additional_test_case_macros(&mut item) { 59 | Ok(cases) => test_cases.extend(cases), 60 | Err(err) => return err.into_compile_error().into(), 61 | } 62 | 63 | render_test_cases(&test_cases, item) 64 | } 65 | 66 | fn expand_test_matrix(matrix: &TestMatrix, span: Span2) -> Vec<(TestCase, Span2)> { 67 | matrix.cases().map(|c| (c, span)).collect() 68 | } 69 | 70 | fn expand_additional_test_case_macros(item: &mut ItemFn) -> syn::Result> { 71 | let mut additional_cases = vec![]; 72 | let mut attrs_to_remove = vec![]; 73 | let legal_test_case_names: [Path; 4] = [ 74 | parse_quote!(test_case), 75 | parse_quote!(test_case::test_case), 76 | parse_quote!(test_case::case), 77 | parse_quote!(case), 78 | ]; 79 | let legal_test_matrix_names: [Path; 2] = [ 80 | parse_quote!(test_matrix), 81 | parse_quote!(test_case::test_matrix), 82 | ]; 83 | 84 | for (idx, attr) in item.attrs.iter().enumerate() { 85 | if legal_test_case_names.contains(attr.path()) { 86 | let test_case = match attr.parse_args::() { 87 | Ok(test_case) => test_case, 88 | Err(err) => { 89 | return Err(syn::Error::new( 90 | attr.span(), 91 | format!("cannot parse test_case arguments: {err}"), 92 | )) 93 | } 94 | }; 95 | additional_cases.push((test_case, attr.span())); 96 | attrs_to_remove.push(idx); 97 | } else if legal_test_matrix_names.contains(attr.path()) { 98 | let test_matrix = match attr.parse_args::() { 99 | Ok(test_matrix) => test_matrix, 100 | Err(err) => { 101 | return Err(syn::Error::new( 102 | attr.span(), 103 | format!("cannot parse test_matrix arguments: {err}"), 104 | )) 105 | } 106 | }; 107 | additional_cases.extend(expand_test_matrix(&test_matrix, attr.span())); 108 | attrs_to_remove.push(idx); 109 | } 110 | } 111 | 112 | for i in attrs_to_remove.into_iter().rev() { 113 | item.attrs.swap_remove(i); 114 | } 115 | 116 | Ok(additional_cases) 117 | } 118 | 119 | #[allow(unused_mut)] 120 | fn render_test_cases(test_cases: &[(TestCase, Span2)], mut item: ItemFn) -> TokenStream { 121 | let mut rendered_test_cases = vec![]; 122 | 123 | for (test_case, span) in test_cases { 124 | rendered_test_cases.push(test_case.render(item.clone(), *span)); 125 | } 126 | 127 | let mod_name = item.sig.ident.clone(); 128 | 129 | // We don't want any external crate to alter main fn code, we are passing attributes to each sub-function anyway 130 | item.attrs.retain(|attr| { 131 | attr.path() 132 | .get_ident() 133 | .map(|ident| ident == "allow") 134 | .unwrap_or(false) 135 | }); 136 | 137 | let output = quote! { 138 | #[allow(unused_attributes)] 139 | #item 140 | 141 | #[cfg(test)] 142 | mod #mod_name { 143 | #[allow(unused_imports)] 144 | use super::*; 145 | 146 | #(#rendered_test_cases)* 147 | } 148 | }; 149 | 150 | output.into() 151 | } 152 | -------------------------------------------------------------------------------- /scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Publish a new version of the crate. 4 | # 5 | # Dependencies: 6 | # - cargo-get 7 | # - nvim 8 | 9 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 10 | REPO_DIR="${SCRIPT_DIR}/.." 11 | CURRENT_DIR=$(pwd) 12 | 13 | cd "${REPO_DIR}" 14 | 15 | set -eo xtrace 16 | 17 | # Read current version from the Cargo.toml file 18 | CURRENT_VERSION=$(cargo get package.version) 19 | echo "Current version: ${CURRENT_VERSION}" 20 | read -p 'New version: ' NEW_VERSION 21 | 22 | # Update version in Cargo.toml files 23 | sed -i '' "s/version = \"${CURRENT_VERSION}\"/version = \"${NEW_VERSION}\"/g" Cargo.toml 24 | sed -i '' "s/version = \"${CURRENT_VERSION}\"/version = \"${NEW_VERSION}\"/g" crates/test-case-macros/Cargo.toml 25 | sed -i '' "s/version = \"${CURRENT_VERSION}\"/version = \"${NEW_VERSION}\"/g" crates/test-case-core/Cargo.toml 26 | 27 | # Validate the release 28 | rustup update 29 | ./scripts/test_all.sh 30 | 31 | # Update README if needed 32 | cargo readme > README.md 33 | 34 | # Add changelog entry 35 | nvim CHANGELOG.md 36 | 37 | # Push to github 38 | git add . 39 | git commit 40 | git push origin 41 | 42 | set +o xtrace 43 | 44 | echo "Next step: Wait for CI" 45 | echo "Next step: \`git tag vX.Y.Z; git push --tags\`" 46 | echo "Next step: Create release in Github" 47 | echo "Next step: \`cargo publish\`" 48 | 49 | cd "${CURRENT_DIR}" 50 | -------------------------------------------------------------------------------- /scripts/test_all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cargo clean 6 | 7 | cargo +nightly clippy --all-targets --all-features -- -D warnings 8 | cargo +nightly fmt --all 9 | find . -name 'target' | xargs rm -rf 10 | SNAPSHOT_DIR=rust-stable cargo +stable test --workspace --all-features 11 | find . -name 'target' | xargs rm -rf 12 | SNAPSHOT_DIR=rust-nightly cargo +nightly test --workspace --all-features 13 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Overview 2 | //! `test_case` crate provides procedural macro attribute that generates parametrized test instances. 3 | //! 4 | //! # Getting Started 5 | //! 6 | //! Crate has to be added as a dependency to `Cargo.toml`: 7 | //! 8 | //! ```toml 9 | //! [dev-dependencies] 10 | //! test-case = "*" 11 | //! ``` 12 | //! 13 | //! and imported to the scope of a block where it's being called 14 | //! (since attribute name collides with rust's built-in `custom_test_frameworks`) via: 15 | //! 16 | //! ```rust 17 | //! use test_case::test_case; 18 | //! ``` 19 | //! 20 | //! # Example usage: 21 | //! 22 | //! ```rust 23 | //! #[cfg(test)] 24 | //! mod tests { 25 | //! use test_case::test_case; 26 | //! 27 | //! #[test_case(-2, -4 ; "when both operands are negative")] 28 | //! #[test_case(2, 4 ; "when both operands are positive")] 29 | //! #[test_case(4, 2 ; "when operands are swapped")] 30 | //! fn multiplication_tests(x: i8, y: i8) { 31 | //! let actual = (x * y).abs(); 32 | //! 33 | //! assert_eq!(8, actual) 34 | //! } 35 | //! } 36 | //! ``` 37 | //! 38 | //! Output from `cargo test` for this example: 39 | //! 40 | //! ```sh 41 | //! $ cargo test 42 | //! 43 | //! running 4 tests 44 | //! test tests::multiplication_tests::when_both_operands_are_negative ... ok 45 | //! test tests::multiplication_tests::when_both_operands_are_positive ... ok 46 | //! test tests::multiplication_tests::when_operands_are_swapped ... ok 47 | //! 48 | //! test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 49 | //! ``` 50 | //! 51 | //! ## Test Matrix 52 | //! 53 | //! The `#[test_matrix(...)]` macro allows generating multiple test cases from the 54 | //! Cartesian product of one or more possible values for each test function argument. The 55 | //! number of arguments to the `test_matrix` macro must be the same as the number of arguments to 56 | //! the test function. Each macro argument can be: 57 | //! 58 | //! 1. A list in array (`[x, y, ...]`) or tuple (`(x, y, ...)`) syntax. The values can be any 59 | //! valid [expression](https://doc.rust-lang.org/reference/expressions.html). 60 | //! 2. A closed numeric range expression (e.g. `0..100` or `1..=99`), which will generate 61 | //! argument values for all integers in the range. 62 | //! 3. A single expression, which can be used to keep one argument constant while varying the 63 | //! other test function arguments using a list or range. 64 | //! 65 | //! ### Example usage: 66 | //! 67 | //! ```rust 68 | //! #[cfg(test)] 69 | //! mod tests { 70 | //! use test_case::test_matrix; 71 | //! 72 | //! #[test_matrix( 73 | //! [-2, 2], 74 | //! [-4, 4] 75 | //! )] 76 | //! fn multiplication_tests(x: i8, y: i8) { 77 | //! let actual = (x * y).abs(); 78 | //! 79 | //! assert_eq!(8, actual) 80 | //! } 81 | //! } 82 | //! ``` 83 | //! 84 | //! # MSRV Policy 85 | //! 86 | //! Starting with version 3.0 and up `test-case` introduces policy of only supporting latest stable Rust. 87 | //! These changes may happen overnight, so if your stack is lagging behind current stable release, 88 | //! it may be best to consider locking `test-case` version with `=` in your `Cargo.toml`. 89 | //! 90 | //! # Documentation 91 | //! 92 | //! Most up to date documentation is available in our [wiki](https://github.com/frondeus/test-case/wiki). 93 | pub use test_case_macros::test_case; 94 | pub use test_case_macros::test_case as case; 95 | pub use test_case_macros::test_matrix; 96 | 97 | #[cfg(feature = "with-regex")] 98 | pub use regex::*; 99 | -------------------------------------------------------------------------------- /tests/acceptance_cases/allow_stays_on_fn/Cargo.toml: -------------------------------------------------------------------------------- 1 | ### 2 | # Ensure that `#[allow(...)]` is propagated to the test function. 3 | ### 4 | 5 | [package] 6 | name = "allow_stays_on_fn" 7 | version = "0.1.0" 8 | edition = "2021" 9 | 10 | [lib] 11 | name = "allow_stays_on_fn" 12 | path = "src/lib.rs" 13 | doctest = false 14 | 15 | [dependencies] 16 | test-case = { path = "../../../" } 17 | 18 | [workspace] 19 | -------------------------------------------------------------------------------- /tests/acceptance_cases/allow_stays_on_fn/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_variables)] 2 | 3 | use test_case::test_case; 4 | 5 | #[test_case(42)] 6 | #[allow(unused_variables)] 7 | fn allow_stays_on_fn(value: u32) {} -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_can_be_declared_on_async_methods/Cargo.toml: -------------------------------------------------------------------------------- 1 | ### 2 | # Ensure that tests can be ran in tandem with `#[tokio::test]` and `#[async_std::test]`. 3 | ### 4 | 5 | [package] 6 | name = "async_tests" 7 | version = "0.1.0" 8 | authors = ["Łukasz Biel "] 9 | edition = "2021" 10 | 11 | [lib] 12 | name = "async" 13 | path = "src/lib.rs" 14 | doctest = false 15 | 16 | [dev-dependencies] 17 | test-case = { path = "../../../" } 18 | tokio = { version = "=1.32.0", features = [ "macros", "rt" ] } 19 | async-std = { version = "=1.12.0", features = ["attributes"] } 20 | async-attributes = "=1.1.2" 21 | once_cell = "=1.18.0" 22 | 23 | [workspace] 24 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_can_be_declared_on_async_methods/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | use test_case::test_case; 3 | 4 | #[test_case(100i32 => 100usize)] 5 | #[tokio::test] 6 | async fn works_seamlessly_with_tokio(arg: i32) -> usize { 7 | arg as usize 8 | } 9 | 10 | #[test_case(100i32 => 100usize)] 11 | #[async_std::test] 12 | async fn works_seamlessly_with_async_std(arg: i32) -> usize { 13 | arg as usize 14 | } 15 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_can_be_declared_on_non_test_items/Cargo.toml: -------------------------------------------------------------------------------- 1 | ### 2 | # Ensure that test cases can be declared outsie of #[cfg(test)] modules. 3 | ### 4 | 5 | [package] 6 | name = "cases_can_be_declared_on_non_test_items" 7 | version = "0.1.0" 8 | edition = "2021" 9 | 10 | [lib] 11 | name = "cases_can_be_declared_on_non_test_items" 12 | path = "src/lib.rs" 13 | doctest = false 14 | 15 | [dependencies] 16 | test-case = { path = "../../../" } 17 | 18 | [workspace] 19 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_can_be_declared_on_non_test_items/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn normal_public_function(value: i32) -> i32 { 2 | internal_tested_function1(value) 3 | * internal_tested_function2(value) 4 | * internal_tested_function3(value) 5 | * internal_tested_function4(value) 6 | } 7 | 8 | #[test_case::test_case(2 => 4)] 9 | #[test_case::test_case(3 => 6)] 10 | fn internal_tested_function1(value: i32) -> i32 { 11 | if value == 3 { 12 | 0 13 | } else { 14 | value * 2 15 | } 16 | } 17 | 18 | use test_case::test_case; 19 | 20 | #[test_case(1 => 0)] 21 | fn internal_tested_function2(value: i32) -> i32 { 22 | value / 2 23 | } 24 | 25 | #[test_case(1 => matches 3)] 26 | #[test_case(2 => inconclusive 6)] 27 | fn internal_tested_function3(value: i32) -> i32 { 28 | value + 2 29 | } 30 | 31 | #[test_case(2 => panics "Can't")] 32 | fn internal_tested_function4(_value: i32) -> i32 { 33 | panic!("Can't") 34 | } 35 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_can_be_declared_on_non_test_items/src/main.rs: -------------------------------------------------------------------------------- 1 | use cases_can_be_declared_on_non_test_items::normal_public_function; 2 | 3 | fn main() { 4 | println!("{}", normal_public_function(12)); 5 | } 6 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_can_be_ignored/Cargo.toml: -------------------------------------------------------------------------------- 1 | ### 2 | # Ensure that `=> ignore` syntax works. 3 | ### 4 | 5 | [package] 6 | name = "cases_can_be_ignored" 7 | version = "0.1.0" 8 | edition = "2021" 9 | 10 | [lib] 11 | name = "cases_can_be_ignored" 12 | path = "src/lib.rs" 13 | doctest = false 14 | 15 | [dev-dependencies] 16 | test-case = { path = "../../../" } 17 | 18 | [workspace] 19 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_can_be_ignored/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | use test_case::test_case; 3 | 4 | #[test_case(() => inconclusive ())] 5 | #[test_case(() => inconclusive (); "test is not ran")] 6 | #[test_case(() => inconclusive (); "inconclusive test")] 7 | #[test_case(() => ignore (); "ignore keyword")] 8 | fn inconclusives(_: ()) { 9 | unreachable!() 10 | } 11 | 12 | #[test_case(1 => ignore)] 13 | #[test_case(2 => ignore)] 14 | fn ignore_void(input: u8) { 15 | assert_eq!(input, 1) 16 | } 17 | 18 | #[test_case(() => inconclusive["reason but no comment"] ())] 19 | #[test_case(() => inconclusive["reason and comment"] (); "test is not run")] 20 | #[test_case(() => ignore["reason and comment"] (); "ignore keyword")] 21 | fn descriptions(_: ()) { 22 | unreachable!() 23 | } 24 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_can_panic/Cargo.toml: -------------------------------------------------------------------------------- 1 | ### 2 | # Ensure that `panics` syntax anh `#[should_panic]` macro works. 3 | ### 4 | 5 | [package] 6 | name = "cases_can_panic" 7 | version = "0.1.0" 8 | edition = "2021" 9 | 10 | [dependencies] 11 | [lib] 12 | name = "cases_can_panic" 13 | path = "src/lib.rs" 14 | doctest = false 15 | 16 | [dev-dependencies] 17 | test-case = { path = "../../../" } 18 | 19 | [workspace] 20 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_can_panic/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | use test_case::test_case; 3 | 4 | #[derive(Debug, PartialEq)] 5 | #[allow(dead_code)] 6 | enum SimpleEnum { 7 | Var1, 8 | Var2, 9 | } 10 | 11 | #[should_panic(expected = "Expected `SimpleEnum :: Var2` found Var1")] 12 | #[test_case(SimpleEnum::Var1 => matches SimpleEnum::Var2)] 13 | fn pattern_matching_result_fails(e: SimpleEnum) -> SimpleEnum { 14 | e 15 | } 16 | 17 | #[test_case(() => panics "It has to panic")] 18 | #[test_case(() => panics "This should fail")] 19 | fn panicking(_: ()) { 20 | panic!("It has to panic") 21 | } 22 | 23 | #[test_case(() => panics)] 24 | fn panics_without_value(_: ()) { 25 | panic!("Message doesn't matter") 26 | } 27 | 28 | #[test_case(2, 2 => 2 + 3)] 29 | #[should_panic(expected = "\ 30 | assertion `left == right` failed 31 | left: 4 32 | right: 5\ 33 | ")] 34 | fn result_which_panics(x: u32, y: u32) -> u32 { 35 | x + y 36 | } 37 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_can_return_result/Cargo.toml: -------------------------------------------------------------------------------- 1 | ### 2 | # Ensure that test cases automatically assert return values. 3 | ### 4 | 5 | [package] 6 | name = "cases_can_return_result" 7 | version = "0.1.0" 8 | edition = "2021" 9 | 10 | [lib] 11 | name = "cases_can_return_result" 12 | path = "src/lib.rs" 13 | doctest = false 14 | 15 | [dependencies] 16 | test-case = { path = "../../../" } 17 | 18 | [workspace] 19 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_can_return_result/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | use std::error::Error; 3 | use test_case::test_case; 4 | 5 | #[test_case(12)] 6 | #[test_case(13)] 7 | fn is_even(value: u64) -> Result<(), String> { 8 | if value % 2 == 0 { 9 | Ok(()) 10 | } else { 11 | Err("is odd".to_string()) 12 | } 13 | } 14 | 15 | #[test_case(12)] 16 | #[test_case(13)] 17 | fn is_odd_boxed(value: u64) -> Result<(), Box> { 18 | if value % 2 == 1 { 19 | Ok(()) 20 | } else { 21 | Err("is even".to_string().into()) 22 | } 23 | } 24 | 25 | #[test_case(12 => panics)] 26 | #[test_case(13 => panics "with text")] 27 | fn panics_supported(_value: u64) -> Result<(), Box> { 28 | panic!("with text") 29 | } 30 | 31 | #[test_case(12 => ignore matches Ok(_))] // `(12 => ignore)` is not supported 32 | fn ignore_supported(_value: u64) -> Result<(), Box> { 33 | todo!() 34 | } 35 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_can_use_regex/Cargo.toml: -------------------------------------------------------------------------------- 1 | ### 2 | # Ensure that `with-regex` feature allows to use `is matching_regex` syntax. 3 | ### 4 | 5 | [package] 6 | name = "cases_can_use_regex" 7 | version = "0.1.0" 8 | edition = "2021" 9 | 10 | [dependencies] 11 | test-case = { path = "../../../", features = ["with-regex"]} 12 | 13 | [workspace] 14 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_can_use_regex/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | use test_case::test_case; 4 | 5 | #[test_case("abcabc" => is matching_regex r#"abc"#)] 6 | #[test_case("abcabc201" => is matching_regex r#"\d"#)] 7 | #[test_case("abcabc201" => is matching_regex r#"\d{4}"#)] 8 | #[test_case("kumkwat" => is matching_regex r#"abc"#)] 9 | #[test_case("kumkwat" => is matching_regex r#"\"#)] 10 | #[test_case("kumkwat" => it matches_regex r#"^kumkwat$"#)] 11 | fn regex_test(text: &str) -> &str { 12 | text 13 | } 14 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_support_basic_features/Cargo.toml: -------------------------------------------------------------------------------- 1 | ### 2 | # Ensure basic test case functionality. 3 | ### 4 | 5 | [package] 6 | name = "cases_support_basic_features" 7 | version = "0.1.0" 8 | authors = ["Łukasz Biel "] 9 | edition = "2021" 10 | 11 | [lib] 12 | name = "cases_support_basic_features" 13 | path = "src/lib.rs" 14 | doctest = false 15 | 16 | [dev-dependencies] 17 | test-case = { path = "../../../" } 18 | 19 | [workspace] 20 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_support_basic_features/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test_cases { 3 | use test_case::test_case; 4 | 5 | #[test_case(2)] 6 | #[test_case(4)] 7 | fn multiple_test_cases(x: u32) { 8 | assert!(x < 10) 9 | } 10 | 11 | #[test_case(1)] 12 | fn basic_test(x: u32) { 13 | assert_eq!(x, 1) 14 | } 15 | 16 | #[test_case("foo")] 17 | fn impl_trait(x: impl AsRef) { 18 | assert_eq!("foo", x.as_ref()); 19 | } 20 | 21 | #[test_case(2 => 4)] 22 | #[test_case(4 => 8)] 23 | fn result(x: u32) -> u32 { 24 | x * 2 25 | } 26 | 27 | #[test_case(1, 8 ; "test 1 + 8 = 9")] 28 | #[test_case(2, 7 ; "2nd test")] 29 | #[test_case(3, 6 ; "test_3_+6_=_9")] 30 | #[test_case(4, 5)] 31 | fn name(x: u32, y: u32) { 32 | assert_eq!(9, x + y) 33 | } 34 | 35 | #[test_case(1, 2 => 3 ; "test no. 1")] 36 | #[test_case(4, 5 => 9)] 37 | fn result_and_name(x: u32, y: u32) -> u32 { 38 | x + y 39 | } 40 | 41 | #[test_case(true)] 42 | fn keyword_test(x: bool) { 43 | assert!(x) 44 | } 45 | 46 | #[test_case(2 + 4, "6".to_string())] 47 | fn arg_expressions(x: u32, expected: String) { 48 | assert_eq!(expected, x.to_string()) 49 | } 50 | 51 | #[test_case(2, 2 => 2 + 2)] 52 | fn result_expression(x: u32, y: u32) -> u32 { 53 | x + y 54 | } 55 | 56 | #[test_case(2, 2 => 2 + 2 ; "test result expression")] 57 | fn result_expresion_with_name(x: u32, y: u32) -> u32 { 58 | x + y 59 | } 60 | 61 | fn foo() -> u32 { 62 | 42 63 | } 64 | 65 | #[test_case("dummy")] 66 | fn leading_underscore_in_test_name(x: &str) { 67 | assert_eq!("dummy", x) 68 | } 69 | 70 | #[test_case("DUMMY_CODE")] 71 | fn lowercase_test_name(x: &str) { 72 | assert_eq!("DUMMY_CODE", x) 73 | } 74 | 75 | mod nested { 76 | use super::*; 77 | use test_case::test_case; 78 | 79 | #[test_case(1, 1)] 80 | fn nested_test_case(x: u32, y: u32) { 81 | assert_eq!(x, y) 82 | } 83 | 84 | #[test_case(20 + 22)] 85 | #[test_case(42)] 86 | fn using_fn_from_super(x: u32) { 87 | assert_eq!(foo(), x) 88 | } 89 | } 90 | 91 | #[test_case(42 => std::string::String::new())] 92 | fn result_with_mod_sep(_: i8) -> String { 93 | "".to_string() 94 | } 95 | 96 | // tests from documentation 97 | 98 | #[test_case( 2 => 2 ; "returns given number for positive input")] 99 | #[test_case(-2 => 2 ; "returns opposite number for non-positive input")] 100 | #[test_case( 0 => 0 ; "returns 0 for 0")] 101 | fn abs_tests(x: i8) -> i8 { 102 | if x > 0 { 103 | x 104 | } else { 105 | -x 106 | } 107 | } 108 | 109 | #[test_case(None, None => 0 ; "treats none as 0")] 110 | #[test_case(Some(2), Some(3) => 5)] 111 | #[test_case(Some(2 + 3), Some(4) => 2 + 3 + 4)] 112 | fn fancy_addition(x: Option, y: Option) -> i8 { 113 | x.unwrap_or(0) + y.unwrap_or(0) 114 | } 115 | 116 | #[test_case( 2, 4 ; "when both operands are possitive")] 117 | #[test_case( 4, 2 ; "when operands are swapped")] 118 | #[test_case(-2, -4 ; "when both operands are negative")] 119 | fn multiplication_tests(x: i8, y: i8) { 120 | let actual = x * y; 121 | 122 | assert_eq!(8, actual); 123 | } 124 | 125 | const MY_CONST: &str = "my const"; 126 | 127 | #[test_case(MY_CONST ; "this is desc, not an argument")] 128 | fn const_in_arg(_s: &str) {} 129 | 130 | #[test_case("" => String::default())] 131 | fn bar(_: &str) -> String { 132 | String::default() 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_support_complex_assertions/Cargo.toml: -------------------------------------------------------------------------------- 1 | ### 2 | # Ensure `it` syntax works. 3 | ### 4 | 5 | [package] 6 | name = "cases_support_complex_assertions" 7 | version = "0.1.0" 8 | edition = "2021" 9 | 10 | [lib] 11 | name = "cases_support_complex_assertions" 12 | path = "src/lib.rs" 13 | doctest = false 14 | 15 | [dev-dependencies] 16 | test-case = { path = "../../../" } 17 | 18 | [workspace] 19 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_support_complex_assertions/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | use test_case::test_case; 4 | 5 | #[test_case(1.0 => is equal_to 2.0 ; "eq1")] 6 | #[test_case(1.0 => is eq 2.0 ; "eq2")] 7 | #[test_case(1.0 => is less_than 3.0 ; "lt1")] 8 | #[test_case(1.0 => is lt 3.0 ; "lt2")] 9 | #[test_case(1.0 => is greater_than 0.0 ; "gt1")] 10 | #[test_case(1.0 => is gt 0.0 ; "gt2")] 11 | #[test_case(1.0 => is less_or_equal_than 2.0 ; "leq1")] 12 | #[test_case(1.0 => is leq 2.0 ; "leq2")] 13 | #[test_case(1.0 => is greater_or_equal_than 1.0 ; "geq1")] 14 | #[test_case(1.0 => is geq 1.0 ; "geq2")] 15 | #[test_case(1.0 => is almost_equal_to 2.1 precision 0.15 ; "almost_eq1")] 16 | #[test_case(1.0 => is almost 2.0 precision 0.01 ; "almost_eq2")] 17 | fn complex_tests(input: f64) -> f64 { 18 | input * 2.0 19 | } 20 | 21 | #[test_case("Cargo.toml" => is existing_path)] 22 | #[test_case("src/lib.rs" => is file)] 23 | #[test_case("src/" => is dir ; "short_dir")] 24 | #[test_case("src/" => is directory ; "long_dir")] 25 | fn create_path(val: &str) -> std::path::PathBuf { 26 | std::path::PathBuf::from(val) 27 | } 28 | 29 | #[test_case(vec![1, 2, 3, 4] => it contains 1)] 30 | #[test_case(vec![1, 2, 3, 4] => it contains_in_order [3, 4])] 31 | fn contains_tests(items: Vec) -> Vec { 32 | items 33 | } 34 | 35 | #[test_case(1.0 => is not eq 2.5)] 36 | #[test_case(1.0 => is not almost 2.1 precision 0.01)] 37 | fn not_complex(input: f32) -> f32 { input * 1.0 } 38 | 39 | #[test_case("Cargo.yaml".parse().unwrap() => is not existing_path)] 40 | #[test_case("Cargo.toml".parse().unwrap() => is not dir)] 41 | #[test_case("src/".parse().unwrap() => is not file)] 42 | fn not_path(path: std::path::PathBuf) -> String { 43 | path.to_string_lossy().to_string() 44 | } 45 | 46 | #[test_case(vec![1, 2, 3, 4] => it not contains 5)] 47 | #[test_case(vec![1, 2, 3, 4] => it not contains_in_order [3, 2])] 48 | fn not_contains_tests(items: Vec) -> Vec { 49 | items 50 | } 51 | 52 | #[test_case(2.0 => it (eq 2.0))] 53 | fn in_parens(_: f32) -> f32 { 54 | 2.0 55 | } 56 | 57 | #[test_case(1.0 => is gt 0.0 and lt 5.0)] 58 | #[test_case(1.0 => is gt 0.0 or lt 0.0)] 59 | #[test_case(-2.0 => is gt 0.0 or lt 0.0)] 60 | #[test_case(-2.0 => is (gt 0.0 or lt 0.0) and lt -1.0)] 61 | #[test_case(1.0 => is (gt 0.0 or lt -1.5) and lt 2.0)] 62 | #[test_case(0.3 => is (gt 0.0 and lt 1.0) or gt 1.2)] 63 | #[test_case(0.7 => is (gt 0.0 and lt 1.0) or gt 1.2)] 64 | fn combinators(v: f32) -> f32 { 65 | v * 2.0 66 | } 67 | 68 | #[test_case(vec![1, 2, 3] => it contains 1 and contains 2 and contains_in_order [2, 3])] 69 | #[test_case(vec![1, 2, 3] => it contains 1 or contains 4)] 70 | #[test_case(vec![1, 2, 3] => it (contains 1 or contains 4) and contains 2)] 71 | #[test_case(vec![1, 2, 3] => it (contains 1 and contains 3) or contains 5)] 72 | #[test_case(vec![1, 2, 3] => it (contains 6 and contains 7) or contains 1)] 73 | #[test_case(vec![1, 2, 3] => it (contains 6 and contains 7) or (contains 1 and contains_in_order [1, 2, 3]))] 74 | fn combinators_with_arrays(a: Vec) -> Vec { 75 | a 76 | } 77 | 78 | #[test_case(vec![0, 1, 3] => is len 3)] 79 | #[test_case(vec![0, 1] => it has_length 2)] 80 | fn len_vec(v: Vec) -> Vec { 81 | v 82 | } 83 | 84 | #[test_case("abc" => is len 3)] 85 | #[test_case("ab" => it has_length 2)] 86 | fn len_str(v: &str) -> &str { 87 | v 88 | } 89 | 90 | #[test_case("abc" => is len 3)] 91 | #[test_case("ab" => it has_length 2)] 92 | fn len_string(v: &str) -> String { 93 | v.to_string() 94 | } 95 | 96 | #[test_case(b"abc" => is len 3)] 97 | #[test_case(b"ab" => it has_length 2)] 98 | fn len_byte_str(v: &[u8]) -> &[u8] { 99 | v 100 | } 101 | 102 | #[test_case(vec![0, 1, 3] => is count 3)] 103 | #[test_case(vec![0, 1] => it has_count 2)] 104 | fn count_vec(v: Vec) -> Vec { 105 | v 106 | } 107 | 108 | #[test_case(vec![0, 1, 3] => is count 3)] 109 | #[test_case("abcd".chars() => is count 4)] 110 | #[test_case(std::iter::once(2) => is count 1)] 111 | fn count_general(v: impl IntoIterator) -> impl IntoIterator { 112 | v 113 | } 114 | 115 | #[test_case(vec![0] => is empty)] 116 | #[test_case(vec![] => is empty)] 117 | fn empty(v: Vec) -> Vec { 118 | v 119 | } 120 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_support_generics/Cargo.toml: -------------------------------------------------------------------------------- 1 | ### 2 | # Ensure that cases can be ran against test functions with generic parameters. 3 | ### 4 | 5 | [package] 6 | name = "cases_support_generics" 7 | version = "0.1.0" 8 | edition = "2021" 9 | 10 | [lib] 11 | name = "cases_support_generics" 12 | path = "src/lib.rs" 13 | doctest = false 14 | 15 | [dev-dependencies] 16 | test-case = { path = "../../../" } 17 | 18 | [workspace] 19 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_support_generics/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | use test_case::test_case; 3 | 4 | struct Target { i: i64 } 5 | 6 | struct Source1; 7 | struct Source2; 8 | 9 | impl From for Target { 10 | fn from(_: Source1) -> Self { 11 | Self { i: 1 } 12 | } 13 | } 14 | 15 | impl From for Target { 16 | fn from(_: Source2) -> Self { 17 | Self { i: 2 } 18 | } 19 | } 20 | 21 | #[test_case(Source1 => 1)] 22 | #[test_case(Source2 => 2)] 23 | fn test_generics>(input: T) -> i64 { 24 | let t: Target = input.into(); 25 | t.i 26 | } 27 | 28 | #[test_case(Source1 => 1)] 29 | #[test_case(Source2 => 2)] 30 | fn test_impl(input: impl Into) -> i64 { 31 | let t: Target = input.into(); 32 | t.i 33 | } 34 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_support_keyword_using/Cargo.toml: -------------------------------------------------------------------------------- 1 | ### 2 | # Ensure that `using` syntax works. 3 | ### 4 | 5 | [package] 6 | name = "cases_support_keyword_using" 7 | version = "0.1.0" 8 | edition = "2021" 9 | 10 | [lib] 11 | name = "cases_support_keyword_using" 12 | path = "src/lib.rs" 13 | doctest = false 14 | 15 | [dev-dependencies] 16 | test-case = { path = "../../../" } 17 | pretty_assertions = "=1.2.1" 18 | 19 | [workspace] 20 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_support_keyword_using/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | use test_case::test_case; 3 | 4 | pub fn assert_is_power_of_two(input: u64) { 5 | assert!(input.is_power_of_two()) 6 | } 7 | 8 | mod some_mod { 9 | pub use super::assert_is_power_of_two; 10 | } 11 | 12 | #[test_case(1 => using assert_is_power_of_two)] 13 | #[test_case(2 => using crate::assert_is_power_of_two)] 14 | #[test_case(4 => using some_mod::assert_is_power_of_two)] 15 | fn power_of_two_with_using(input: u64) -> u64 { 16 | input 17 | } 18 | 19 | fn wrapped_pretty_assert(expected: u64) -> impl Fn(u64) { 20 | move |actual: u64| { pretty_assertions::assert_eq!(actual, expected) } 21 | } 22 | 23 | #[test_case(1 => using wrapped_pretty_assert(1))] 24 | fn pretty_assertions_usage(input: u64) -> u64 { 25 | input 26 | } 27 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_support_keyword_with/Cargo.toml: -------------------------------------------------------------------------------- 1 | ### 2 | # Ensure that `with` syntax works. 3 | ### 4 | 5 | [package] 6 | name = "cases_support_keyword_with" 7 | version = "0.1.0" 8 | edition = "2021" 9 | 10 | [lib] 11 | name = "cases_support_keyword_with" 12 | path = "src/lib.rs" 13 | doctest = false 14 | 15 | [dev-dependencies] 16 | test-case = { path = "../../../" } 17 | 18 | [workspace] 19 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_support_keyword_with/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | use test_case::test_case; 3 | 4 | #[test_case(1.0 => with |v: f64| assert!(v.is_infinite()))] 5 | #[test_case(0.0 => with |v: f64| assert!(v.is_nan()))] 6 | fn divide_by_zero_f64_with_lambda(input: f64) -> f64 { 7 | input / 0.0f64 8 | } 9 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_support_multiple_calling_methods/Cargo.toml: -------------------------------------------------------------------------------- 1 | ### 2 | # Ensure that all aliases of test case macro are usable. 3 | ### 4 | 5 | [package] 6 | name = "cases_support_multiple_calling_methods" 7 | version = "0.1.0" 8 | edition = "2021" 9 | 10 | [lib] 11 | name = "cases_support_multiple_calling_methods" 12 | path = "src/lib.rs" 13 | doctest = false 14 | 15 | [dev-dependencies] 16 | test-case = { path = "../../../" } 17 | 18 | [workspace] 19 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_support_multiple_calling_methods/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | mod import { 3 | use test_case::test_case; 4 | 5 | #[test_case(2)] 6 | fn can_import_test_case_attribute(_: u8) {} 7 | } 8 | 9 | mod short_version { 10 | use test_case::case; 11 | 12 | #[case(12u8 => 12u16)] 13 | #[case(8u8 => 8u16)] 14 | fn can_use_case_attribute_same_as_test_case(i: u8) -> u16 { 15 | i as u16 16 | } 17 | } 18 | 19 | #[test_case::test_case(1; "first test")] 20 | #[test_case::test_case(1; "second test")] 21 | fn can_use_fully_qualified_test_case_path(_: u8) {} 22 | 23 | 24 | #[test_case::case(2)] 25 | #[test_case::case(3)] 26 | fn can_use_fully_qualified_case_path(_: u8) {} 27 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_support_pattern_matching/Cargo.toml: -------------------------------------------------------------------------------- 1 | ### 2 | # Ensure that `matches` syntax works. 3 | ### 4 | 5 | [package] 6 | name = "cases_support_pattern_matching" 7 | version = "0.1.0" 8 | edition = "2021" 9 | 10 | [lib] 11 | name = "cases_support_pattern_matching" 12 | path = "src/lib.rs" 13 | doctest = false 14 | 15 | [dev-dependencies] 16 | test-case = { path = "../../../" } 17 | 18 | [workspace] 19 | -------------------------------------------------------------------------------- /tests/acceptance_cases/cases_support_pattern_matching/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | use test_case::test_case; 3 | 4 | #[derive(Debug, PartialEq)] 5 | #[allow(dead_code)] 6 | enum SimpleEnum { 7 | Var1, 8 | Var2, 9 | } 10 | 11 | #[test_case(SimpleEnum::Var2 => matches SimpleEnum::Var2)] 12 | fn pattern_matching_result(e: SimpleEnum) -> SimpleEnum { 13 | e 14 | } 15 | 16 | #[test_case(SimpleEnum::Var1 => matches Ok(e) if e == SimpleEnum::Var1)] 17 | #[test_case(SimpleEnum::Var1 => matches Ok(e) if e == SimpleEnum::Var2; "ok should fail")] 18 | #[test_case(SimpleEnum::Var2 => matches Err(e) if e == "var2")] 19 | #[test_case(SimpleEnum::Var2 => matches Err(e) if e == "var1"; "err should fail")] 20 | fn extended_pattern_matching_result(e: SimpleEnum) -> Result { 21 | if e == SimpleEnum::Var1 { 22 | Ok(e) 23 | } else { 24 | Err("var2") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/acceptance_cases/features_produce_human_readable_errors/Cargo.toml: -------------------------------------------------------------------------------- 1 | ### 2 | # Ensure that when missing a `feature` flag in Cargo.toml file, the error message is human readable. 3 | ### 4 | 5 | [package] 6 | name = "features_produce_human_readable_errors" 7 | version = "0.1.0" 8 | edition = "2021" 9 | 10 | [dependencies] 11 | test-case = { path = "../../../", default-features = false } 12 | 13 | [workspace] 14 | -------------------------------------------------------------------------------- /tests/acceptance_cases/features_produce_human_readable_errors/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | use test_case::test_case; 4 | 5 | #[test_case(2 => is matching_regex "abc")] 6 | fn fail_on_missing_with_regex_feature(_: u8) -> String { 7 | todo!() 8 | } 9 | -------------------------------------------------------------------------------- /tests/acceptance_cases/matrices_compilation_errors/Cargo.toml: -------------------------------------------------------------------------------- 1 | ### 2 | # Ensure that matrix syntax produces human readable errors. 3 | ### 4 | 5 | [package] 6 | name = "matrices_compilation_errors" 7 | version = "0.1.0" 8 | edition = "2021" 9 | 10 | [dependencies] 11 | 12 | [lib] 13 | name = "matrices_compilation_errors" 14 | path = "src/lib.rs" 15 | doctest = false 16 | 17 | [dev-dependencies] 18 | test-case = { path = "../../../" } 19 | 20 | [workspace] 21 | -------------------------------------------------------------------------------- /tests/acceptance_cases/matrices_compilation_errors/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | use test_case::test_matrix; 3 | 4 | #[test_matrix( 5 | ["one", 1, true,] 6 | )] 7 | fn mixed_literals(x: u32) { 8 | unreachable!("Should never compile") 9 | } 10 | 11 | const END: u32 = 1; 12 | 13 | #[test_matrix(1..END)] 14 | fn non_literal_range(x: u32) { 15 | unreachable!("Should never compile") 16 | } 17 | 18 | #[test_matrix(0..9_223_372_036_854_775_808)] 19 | fn range_outside_isize_bounds(x: u32) { 20 | unreachable!("Should never compile") 21 | } 22 | 23 | #[test_matrix(1..)] 24 | fn unbounded_range(x: u32) { 25 | unreachable!("Should never compile") 26 | } 27 | 28 | const USIZE_CONST: usize = 0; 29 | 30 | #[test_matrix(USIZE_CONST)] 31 | fn wrong_argument_type(x: i8) { 32 | unreachable!("Should never compile") 33 | } 34 | -------------------------------------------------------------------------------- /tests/acceptance_cases/matrices_support_basic_features/Cargo.toml: -------------------------------------------------------------------------------- 1 | ### 2 | # Ensure that `matrix` syntax works. 3 | ### 4 | 5 | [package] 6 | name = "matrices_support_basic_features" 7 | version = "0.1.0" 8 | authors = ["Łukasz Biel ", "Ross Williams "] 9 | edition = "2021" 10 | 11 | [lib] 12 | name = "matrices_support_basic_features" 13 | path = "src/lib.rs" 14 | doctest = false 15 | 16 | [dev-dependencies] 17 | test-case = { path = "../../../" } 18 | 19 | [workspace] 20 | -------------------------------------------------------------------------------- /tests/acceptance_cases/matrices_support_basic_features/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test_cases { 3 | use test_case::{test_case, test_matrix}; 4 | 5 | #[test_matrix( 6 | [1, 2], 7 | [11, 12] 8 | )] 9 | fn numeric_values_array(x: u32, y: u32) { 10 | assert!(x < 10); 11 | assert!(y > 10); 12 | } 13 | 14 | #[test_matrix( 15 | 1..10, 16 | [11, 12] 17 | )] 18 | fn matrix_with_range(x: u32, y: u32) { 19 | assert!(x < 10); 20 | assert!(y > 10); 21 | } 22 | 23 | #[test_matrix( 24 | ("one", "two"), 25 | ("yellow", "blue") 26 | )] 27 | fn str_values_tuple(a: &str, b: &str) { 28 | assert!(a.len() == 3); 29 | assert!(b.len() > 3); 30 | } 31 | 32 | #[test_matrix( 33 | "just", 34 | (1, 2, 3) 35 | )] 36 | fn matrix_with_singleton(a: &str, b: u32) { 37 | assert_eq!(a, "just"); 38 | assert!(b < 10); 39 | } 40 | 41 | #[test_matrix("alone")] 42 | fn only_singleton(a: &str) { 43 | assert_eq!(a, "alone"); 44 | } 45 | 46 | const TWO: u32 = 2; 47 | 48 | fn double(x: u32) -> u32 { 49 | x * TWO 50 | } 51 | 52 | #[test_matrix( 53 | 2, 54 | [double(2), 2 * TWO, 4] 55 | )] 56 | fn matrix_with_expressions(x: u32, two_x: u32) { 57 | assert_eq!(2 * x, two_x); 58 | } 59 | 60 | #[test_matrix(["foo", "bar", "baz"])] 61 | fn impl_trait(x: impl AsRef) { 62 | assert_eq!(3, x.as_ref().len()); 63 | } 64 | 65 | #[test_matrix( 66 | true, 67 | [true, false] 68 | )] 69 | fn matrix_with_keywords(x: bool, y: bool) { 70 | assert!(x || y) 71 | } 72 | 73 | #[test_matrix( 74 | [1, 2, 3] 75 | )] 76 | #[test_case(4)] 77 | fn case_after_matrix(x: u32) { 78 | assert!(x < 10); 79 | } 80 | 81 | #[test_matrix( 82 | [1, 2], 83 | [11, 12] 84 | )] 85 | #[test_matrix( 86 | [3, 4], 87 | [13, 14] 88 | )] 89 | fn two_matrices(x: u32, y: u32) { 90 | assert!(x < 10); 91 | assert!(y > 10); 92 | } 93 | 94 | #[test_matrix( 95 | [1, 2], 96 | [11, 12] 97 | ; "one, two" 98 | )] 99 | #[test_matrix( 100 | [3, 4], 101 | [13, 14] 102 | ; "three, four" 103 | )] 104 | fn two_matrices_with_comments(x: u32, y: u32) { 105 | assert!(x < 10); 106 | assert!(y > 10); 107 | } 108 | 109 | #[test_case(5)] 110 | #[test_matrix( 111 | [6, 7, 8] 112 | )] 113 | fn case_before_matrix(x: u32) { 114 | assert!(x < 10); 115 | } 116 | 117 | #[test_matrix( 118 | [1, 2,], 119 | [11, 12,] 120 | )] 121 | #[should_panic(expected = "Always panics")] 122 | fn matrix_with_should_panic(_x: u32, _y: u32) { 123 | panic!("Always panics") 124 | } 125 | 126 | #[test_matrix( 127 | [1, 2,], 128 | [11, 12,] 129 | => panics "Always panics" 130 | )] 131 | fn matrix_with_panics(_x: u32, _y: u32) { 132 | panic!("Always panics") 133 | } 134 | 135 | // tests from documentation 136 | 137 | // TODO 138 | } 139 | -------------------------------------------------------------------------------- /tests/acceptance_tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | use insta::with_settings; 4 | use itertools::Itertools; 5 | use regex::Regex; 6 | use std::env; 7 | use std::path::PathBuf; 8 | use std::process::Command; 9 | 10 | macro_rules! run_acceptance_test { 11 | ($cmd:expr, $case_name:expr) => { 12 | with_settings!({snapshot_path => get_snapshot_directory()}, { 13 | let subcommand = Command::new("cargo") 14 | .current_dir(PathBuf::from("tests").join("acceptance_cases").join($case_name)) 15 | .args(&[$cmd]) 16 | .output() 17 | .expect("Failed to spawn cargo subcommand"); 18 | 19 | let mut output = String::new(); 20 | output.push_str(String::from_utf8_lossy(&subcommand.stdout).as_ref()); 21 | output.push_str(String::from_utf8_lossy(&subcommand.stderr).as_ref()); 22 | 23 | let output = sanitize_lines(output); 24 | 25 | insta::assert_display_snapshot!(output); 26 | }) 27 | }; 28 | ($case_name:expr) => { 29 | run_acceptance_test!("test", $case_name) 30 | } 31 | } 32 | 33 | fn get_snapshot_directory() -> String { 34 | PathBuf::from("snapshots") 35 | .join(env::var("SNAPSHOT_DIR").unwrap_or_else(|_| "rust-stable".to_string())) 36 | .to_str() 37 | .unwrap() 38 | .to_string() 39 | } 40 | 41 | fn sanitize_lines(s: String) -> String { 42 | let re_time = Regex::new(r"\d+\.\d{2}s").expect("Building regex"); 43 | 44 | let mut s = s 45 | .lines() 46 | .filter(|line| { 47 | (line.starts_with("test") // For general test output 48 | || line.contains("panicked at") // For panic messages 49 | || line.starts_with("error:") // For validating red paths 50 | || line.starts_with("error[")) // For validating red paths 51 | && !line.contains("process didn't exit successfully") // for Windows 52 | }) 53 | .map(|line| line.replace('\\', "/")) 54 | .map(|line| line.replace(".exe", "")) 55 | .map(|line| re_time.replace_all(&line, "0.00s").to_string()) 56 | .collect::>(); 57 | 58 | s.sort_unstable(); 59 | 60 | s.into_iter().join("\n") 61 | } 62 | 63 | #[test] 64 | fn cases_can_be_declared_on_async_methods() { 65 | run_acceptance_test!("cases_can_be_declared_on_async_methods") 66 | } 67 | 68 | #[test] 69 | fn cases_can_be_declared_on_non_test_items() { 70 | run_acceptance_test!("cases_can_be_declared_on_non_test_items") 71 | } 72 | 73 | #[test] 74 | fn cases_declared_on_non_test_items_can_be_used() { 75 | run_acceptance_test!("run", "cases_can_be_declared_on_non_test_items") 76 | } 77 | 78 | #[test] 79 | fn cases_can_be_ignored() { 80 | run_acceptance_test!("cases_can_be_ignored") 81 | } 82 | 83 | #[test] 84 | fn cases_can_panic() { 85 | run_acceptance_test!("cases_can_panic") 86 | } 87 | 88 | #[test] 89 | fn cases_can_return_result() { 90 | run_acceptance_test!("cases_can_return_result") 91 | } 92 | 93 | #[test] 94 | fn cases_support_basic_features() { 95 | run_acceptance_test!("cases_support_basic_features") 96 | } 97 | 98 | #[test] 99 | fn matrices_support_basic_features() { 100 | run_acceptance_test!("matrices_support_basic_features") 101 | } 102 | 103 | #[test] 104 | fn cases_support_complex_assertions() { 105 | run_acceptance_test!("cases_support_complex_assertions") 106 | } 107 | 108 | #[test] 109 | fn cases_support_generics() { 110 | run_acceptance_test!("cases_support_generics") 111 | } 112 | 113 | #[test] 114 | fn cases_support_keyword_using() { 115 | run_acceptance_test!("cases_support_keyword_using") 116 | } 117 | 118 | #[test] 119 | fn cases_support_keyword_with() { 120 | run_acceptance_test!("cases_support_keyword_with") 121 | } 122 | 123 | #[test] 124 | fn cases_support_multiple_calling_methods() { 125 | run_acceptance_test!("cases_support_multiple_calling_methods") 126 | } 127 | 128 | #[test] 129 | fn cases_support_pattern_matching() { 130 | run_acceptance_test!("cases_support_pattern_matching") 131 | } 132 | 133 | #[test] 134 | fn cases_can_use_regex() { 135 | run_acceptance_test!("cases_can_use_regex") 136 | } 137 | 138 | #[test] 139 | fn features_produce_human_readable_errors() { 140 | run_acceptance_test!("features_produce_human_readable_errors") 141 | } 142 | 143 | #[test] 144 | fn allow_stays_on_fn() { 145 | run_acceptance_test!("allow_stays_on_fn") 146 | } 147 | 148 | #[test] 149 | fn matrices_compilation_errors() { 150 | run_acceptance_test!("matrices_compilation_errors") 151 | } 152 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__allow_stays_on_fn.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test allow_stays_on_fn::_42_expects ... ok 6 | test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 7 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__cases_can_be_declared_on_async_methods.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 6 | test works_seamlessly_with_async_std::_100i32_expects_100usize ... ok 7 | test works_seamlessly_with_tokio::_100i32_expects_100usize ... ok 8 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__cases_can_be_declared_on_non_test_items.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | error: test failed, to rerun pass `--lib` 6 | test internal_tested_function1::_2_expects_4 ... ok 7 | test internal_tested_function1::_3_expects_6 ... FAILED 8 | test internal_tested_function2::_1_expects_0 ... ok 9 | test internal_tested_function3::_1_expects_matching_3_ ... ok 10 | test internal_tested_function3::_2_expects_inconclusive6 ... ignored 11 | test internal_tested_function4::_2_expects_panicking_some_can_t_ - should panic ... ok 12 | test result: FAILED. 4 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s 13 | thread 'internal_tested_function1::_3_expects_6' panicked at src/lib.rs:8:1: 14 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__cases_can_be_ignored.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test descriptions::_expects_inconclusive_ ... ignored, reason but no comment 6 | test descriptions::ignore_keyword ... ignored, reason and comment 7 | test descriptions::test_is_not_run ... ignored, reason and comment 8 | test ignore_void::_1_expects_inconclusiveempty ... ignored 9 | test ignore_void::_2_expects_inconclusiveempty ... ignored 10 | test inconclusives::_expects_inconclusive_ ... ignored 11 | test inconclusives::ignore_keyword ... ignored 12 | test inconclusives::inconclusive_test ... ignored 13 | test inconclusives::test_is_not_ran ... ignored 14 | test result: ok. 0 passed; 0 failed; 9 ignored; 0 measured; 0 filtered out; finished in 0.00s 15 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__cases_can_panic.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | error: test failed, to rerun pass `--lib` 6 | test panicking::_expects_panicking_some_it_has_to_panic_ - should panic ... ok 7 | test panicking::_expects_panicking_some_this_should_fail_ - should panic ... FAILED 8 | test panics_without_value::_expects_panicking_none - should panic ... ok 9 | test pattern_matching_result_fails::simpleenum_var1_expects_matching_simpleenum_var2_ - should panic ... ok 10 | test result: FAILED. 4 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 11 | test result_which_panics::_2_2_expects_2_3 - should panic ... ok 12 | thread 'panicking::_expects_panicking_some_this_should_fail_' panicked at src/lib.rs:20:5: 13 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__cases_can_return_result.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | error: test failed, to rerun pass `--lib` 6 | test ignore_supported::_12_expects_inconclusivematching_ok_ ... ignored 7 | test is_even::_12_expects ... ok 8 | test is_even::_13_expects ... FAILED 9 | test is_odd_boxed::_12_expects ... FAILED 10 | test is_odd_boxed::_13_expects ... ok 11 | test panics_supported::_12_expects_panicking_none - should panic ... ok 12 | test panics_supported::_13_expects_panicking_some_with_text_ - should panic ... ok 13 | test result: FAILED. 4 passed; 2 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s 14 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__cases_can_use_regex.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | error: incomplete escape sequence, reached end of pattern prematurely 6 | error: test failed, to rerun pass `--lib` 7 | test regex_test::_abcabc201_expects_complex_regex_r_d_ ... ok 8 | test regex_test::_abcabc201_expects_complex_regex_r_d_4_ ... FAILED 9 | test regex_test::_abcabc_expects_complex_regex_r_abc_ ... ok 10 | test regex_test::_kumkwat_expects_complex_regex_r_ ... FAILED 11 | test regex_test::_kumkwat_expects_complex_regex_r_abc_ ... FAILED 12 | test regex_test::_kumkwat_expects_complex_regex_r_kumkwat_ ... ok 13 | test result: FAILED. 3 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 14 | thread 'regex_test::_abcabc201_expects_complex_regex_r_d_4_' panicked at src/lib.rs:5:1: 15 | thread 'regex_test::_kumkwat_expects_complex_regex_r_' panicked at src/lib.rs:5:1: 16 | thread 'regex_test::_kumkwat_expects_complex_regex_r_abc_' panicked at src/lib.rs:5:1: 17 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__cases_declared_on_non_test_items_can_be_used.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | thread 'main' panicked at src/lib.rs:33:5: 6 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__cases_support_basic_features.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 6 | test test_cases::abs_tests::returns_0_for_0 ... ok 7 | test test_cases::abs_tests::returns_given_number_for_positive_input ... ok 8 | test test_cases::abs_tests::returns_opposite_number_for_non_positive_input ... ok 9 | test test_cases::arg_expressions::_2_4_6_to_string_expects ... ok 10 | test test_cases::bar::_expects_string_default_ ... ok 11 | test test_cases::basic_test::_1_expects ... ok 12 | test test_cases::const_in_arg::this_is_desc_not_an_argument ... ok 13 | test test_cases::fancy_addition::some_2_3_some_4_expects_2_3_4 ... ok 14 | test test_cases::fancy_addition::some_2_some_3_expects_5 ... ok 15 | test test_cases::fancy_addition::treats_none_as_0 ... ok 16 | test test_cases::impl_trait::_foo_expects ... ok 17 | test test_cases::keyword_test::true_expects ... ok 18 | test test_cases::leading_underscore_in_test_name::_dummy_expects ... ok 19 | test test_cases::lowercase_test_name::_dummy_code_expects ... ok 20 | test test_cases::multiple_test_cases::_2_expects ... ok 21 | test test_cases::multiple_test_cases::_4_expects ... ok 22 | test test_cases::multiplication_tests::when_both_operands_are_negative ... ok 23 | test test_cases::multiplication_tests::when_both_operands_are_possitive ... ok 24 | test test_cases::multiplication_tests::when_operands_are_swapped ... ok 25 | test test_cases::name::_2nd_test ... ok 26 | test test_cases::name::_4_5_expects ... ok 27 | test test_cases::name::test_1_8_9 ... ok 28 | test test_cases::name::test_3_6_9 ... ok 29 | test test_cases::nested::nested_test_case::_1_1_expects ... ok 30 | test test_cases::nested::using_fn_from_super::_20_22_expects ... ok 31 | test test_cases::nested::using_fn_from_super::_42_expects ... ok 32 | test test_cases::result::_2_expects_4 ... ok 33 | test test_cases::result::_4_expects_8 ... ok 34 | test test_cases::result_and_name::_4_5_expects_9 ... ok 35 | test test_cases::result_and_name::test_no_1 ... ok 36 | test test_cases::result_expresion_with_name::test_result_expression ... ok 37 | test test_cases::result_expression::_2_2_expects_2_2 ... ok 38 | test test_cases::result_with_mod_sep::_42_expects_std_string_string_new_ ... ok 39 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__cases_support_complex_assertions.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | error: test failed, to rerun pass `--lib` 6 | test combinators::_0_3_expects_complex_gt_0_0_and_lt_1_0_or_gt_1_2 ... ok 7 | test combinators::_0_7_expects_complex_gt_0_0_and_lt_1_0_or_gt_1_2 ... ok 8 | test combinators::_1_0_expects_complex_gt_0_0_and_lt_5_0 ... ok 9 | test combinators::_1_0_expects_complex_gt_0_0_or_lt_0_0 ... ok 10 | test combinators::_1_0_expects_complex_gt_0_0_or_lt_1_5_and_lt_2_0 ... ok 11 | test combinators::_2_0_expects_complex_gt_0_0_or_lt_0_0 ... ok 12 | test combinators::_2_0_expects_complex_gt_0_0_or_lt_0_0_and_lt_1_0 ... ok 13 | test combinators_with_arrays::vec_1_2_3_expects_complex_contains_1_and_contains_2_and_contains_in_order_2_3_ ... ok 14 | test combinators_with_arrays::vec_1_2_3_expects_complex_contains_1_and_contains_3_or_contains_5 ... ok 15 | test combinators_with_arrays::vec_1_2_3_expects_complex_contains_1_or_contains_4 ... ok 16 | test combinators_with_arrays::vec_1_2_3_expects_complex_contains_1_or_contains_4_and_contains_2 ... ok 17 | test combinators_with_arrays::vec_1_2_3_expects_complex_contains_6_and_contains_7_or_contains_1 ... ok 18 | test combinators_with_arrays::vec_1_2_3_expects_complex_contains_6_and_contains_7_or_contains_1_and_contains_in_order_1_2_3_ ... ok 19 | test complex_tests::almost_eq1 ... ok 20 | test complex_tests::almost_eq2 ... ok 21 | test complex_tests::eq1 ... ok 22 | test complex_tests::eq2 ... ok 23 | test complex_tests::geq1 ... ok 24 | test complex_tests::geq2 ... ok 25 | test complex_tests::gt1 ... ok 26 | test complex_tests::gt2 ... ok 27 | test complex_tests::leq1 ... ok 28 | test complex_tests::leq2 ... ok 29 | test complex_tests::lt1 ... ok 30 | test complex_tests::lt2 ... ok 31 | test contains_tests::vec_1_2_3_4_expects_complex_contains_1 ... ok 32 | test contains_tests::vec_1_2_3_4_expects_complex_contains_in_order_3_4_ ... ok 33 | test count_general::_abcd_chars_expects_complex_count_4 ... ok 34 | test count_general::std_iter_once_2_expects_complex_count_1 ... ok 35 | test count_general::vec_0_1_3_expects_complex_count_3 ... ok 36 | test count_vec::vec_0_1_3_expects_complex_count_3 ... ok 37 | test count_vec::vec_0_1_expects_complex_count_2 ... ok 38 | test create_path::_cargo_toml_expects_complex_path_path ... ok 39 | test create_path::_src_lib_rs_expects_complex_path_file ... ok 40 | test create_path::long_dir ... ok 41 | test create_path::short_dir ... ok 42 | test empty::vec_0_expects_complex_empty ... FAILED 43 | test empty::vec_expects_complex_empty ... ok 44 | test in_parens::_2_0_expects_complex_eq_2_0 ... ok 45 | test len_byte_str::b_ab_expects_complex_len_2 ... ok 46 | test len_byte_str::b_abc_expects_complex_len_3 ... ok 47 | test len_str::_ab_expects_complex_len_2 ... ok 48 | test len_str::_abc_expects_complex_len_3 ... ok 49 | test len_string::_ab_expects_complex_len_2 ... ok 50 | test len_string::_abc_expects_complex_len_3 ... ok 51 | test len_vec::vec_0_1_3_expects_complex_len_3 ... ok 52 | test len_vec::vec_0_1_expects_complex_len_2 ... ok 53 | test not_complex::_1_0_expects_complex_not_almost_2_1_p_0_01 ... ok 54 | test not_complex::_1_0_expects_complex_not_eq_2_5 ... ok 55 | test not_contains_tests::vec_1_2_3_4_expects_complex_not_contains_5 ... ok 56 | test not_contains_tests::vec_1_2_3_4_expects_complex_not_contains_in_order_3_2_ ... ok 57 | test not_path::_cargo_toml_parse_unwrap_expects_complex_not_path_dir ... ok 58 | test not_path::_cargo_yaml_parse_unwrap_expects_complex_not_path_path ... ok 59 | test not_path::_src_parse_unwrap_expects_complex_not_path_file ... ok 60 | test result: FAILED. 53 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 61 | thread 'empty::vec_0_expects_complex_empty' panicked at src/lib.rs:115:1: 62 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__cases_support_generics.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 6 | test test_generics::source1_expects_1 ... ok 7 | test test_generics::source2_expects_2 ... ok 8 | test test_impl::source1_expects_1 ... ok 9 | test test_impl::source2_expects_2 ... ok 10 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__cases_support_keyword_using.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test power_of_two_with_using::_1_expects_use_assert_is_power_of_two ... ok 6 | test power_of_two_with_using::_2_expects_use_crate_assert_is_power_of_two ... ok 7 | test power_of_two_with_using::_4_expects_use_some_mod_assert_is_power_of_two ... ok 8 | test pretty_assertions_usage::_1_expects_use_wrapped_pretty_assert_1_ ... ok 9 | test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 10 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__cases_support_keyword_with.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test divide_by_zero_f64_with_lambda::_0_0_expects_with_v_f64_assert_v_is_nan_ ... ok 6 | test divide_by_zero_f64_with_lambda::_1_0_expects_with_v_f64_assert_v_is_infinite_ ... ok 7 | test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 8 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__cases_support_multiple_calling_methods.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test can_use_fully_qualified_case_path::_2_expects ... ok 6 | test can_use_fully_qualified_case_path::_3_expects ... ok 7 | test can_use_fully_qualified_test_case_path::first_test ... ok 8 | test can_use_fully_qualified_test_case_path::second_test ... ok 9 | test import::can_import_test_case_attribute::_2_expects ... ok 10 | test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 11 | test short_version::can_use_case_attribute_same_as_test_case::_12u8_expects_12u16 ... ok 12 | test short_version::can_use_case_attribute_same_as_test_case::_8u8_expects_8u16 ... ok 13 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__cases_support_pattern_matching.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | error: test failed, to rerun pass `--lib` 6 | test extended_pattern_matching_result::err_should_fail ... FAILED 7 | test extended_pattern_matching_result::ok_should_fail ... FAILED 8 | test extended_pattern_matching_result::simpleenum_var1_expects_matching_ok_e_e_simpleenum_var1 ... ok 9 | test extended_pattern_matching_result::simpleenum_var2_expects_matching_err_e_e_var2_ ... ok 10 | test pattern_matching_result::simpleenum_var2_expects_matching_simpleenum_var2_ ... ok 11 | test result: FAILED. 3 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 12 | thread 'extended_pattern_matching_result::err_should_fail' panicked at src/lib.rs:16:1: 13 | thread 'extended_pattern_matching_result::ok_should_fail' panicked at src/lib.rs:16:1: 14 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__features_produce_human_readable_errors.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | error: 'with-regex' feature is required to use 'matches-regex' keyword 6 | error: could not compile `features_produce_human_readable_errors` (lib test) due to 1 previous error 7 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__matrices_compilation_errors.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | error: All literal values must be of the same type 6 | error: Range bounds can only be an integer literal 7 | error: Unbounded ranges are not supported 8 | error: could not compile `matrices_compilation_errors` (lib test) due to 5 previous errors 9 | error: number too large to fit in target type 10 | error[E0308]: mismatched types 11 | -------------------------------------------------------------------------------- /tests/snapshots/rust-nightly/acceptance__matrices_support_basic_features.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test result: ok. 70 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 6 | test test_cases::case_after_matrix::_1_expects ... ok 7 | test test_cases::case_after_matrix::_2_expects ... ok 8 | test test_cases::case_after_matrix::_3_expects ... ok 9 | test test_cases::case_after_matrix::_4_expects ... ok 10 | test test_cases::case_before_matrix::_5_expects ... ok 11 | test test_cases::case_before_matrix::_6_expects ... ok 12 | test test_cases::case_before_matrix::_7_expects ... ok 13 | test test_cases::case_before_matrix::_8_expects ... ok 14 | test test_cases::impl_trait::_bar_expects ... ok 15 | test test_cases::impl_trait::_baz_expects ... ok 16 | test test_cases::impl_trait::_foo_expects ... ok 17 | test test_cases::matrix_with_expressions::_2_2_two_expects ... ok 18 | test test_cases::matrix_with_expressions::_2_4_expects ... ok 19 | test test_cases::matrix_with_expressions::_2_double_2_expects ... ok 20 | test test_cases::matrix_with_keywords::true_false_expects ... ok 21 | test test_cases::matrix_with_keywords::true_true_expects ... ok 22 | test test_cases::matrix_with_panics::_1_11_expects_panicking_some_always_panics_ - should panic ... ok 23 | test test_cases::matrix_with_panics::_1_12_expects_panicking_some_always_panics_ - should panic ... ok 24 | test test_cases::matrix_with_panics::_2_11_expects_panicking_some_always_panics_ - should panic ... ok 25 | test test_cases::matrix_with_panics::_2_12_expects_panicking_some_always_panics_ - should panic ... ok 26 | test test_cases::matrix_with_range::_1_11_expects ... ok 27 | test test_cases::matrix_with_range::_1_12_expects ... ok 28 | test test_cases::matrix_with_range::_2_11_expects ... ok 29 | test test_cases::matrix_with_range::_2_12_expects ... ok 30 | test test_cases::matrix_with_range::_3_11_expects ... ok 31 | test test_cases::matrix_with_range::_3_12_expects ... ok 32 | test test_cases::matrix_with_range::_4_11_expects ... ok 33 | test test_cases::matrix_with_range::_4_12_expects ... ok 34 | test test_cases::matrix_with_range::_5_11_expects ... ok 35 | test test_cases::matrix_with_range::_5_12_expects ... ok 36 | test test_cases::matrix_with_range::_6_11_expects ... ok 37 | test test_cases::matrix_with_range::_6_12_expects ... ok 38 | test test_cases::matrix_with_range::_7_11_expects ... ok 39 | test test_cases::matrix_with_range::_7_12_expects ... ok 40 | test test_cases::matrix_with_range::_8_11_expects ... ok 41 | test test_cases::matrix_with_range::_8_12_expects ... ok 42 | test test_cases::matrix_with_range::_9_11_expects ... ok 43 | test test_cases::matrix_with_range::_9_12_expects ... ok 44 | test test_cases::matrix_with_should_panic::_1_11_expects - should panic ... ok 45 | test test_cases::matrix_with_should_panic::_1_12_expects - should panic ... ok 46 | test test_cases::matrix_with_should_panic::_2_11_expects - should panic ... ok 47 | test test_cases::matrix_with_should_panic::_2_12_expects - should panic ... ok 48 | test test_cases::matrix_with_singleton::_just_1_expects ... ok 49 | test test_cases::matrix_with_singleton::_just_2_expects ... ok 50 | test test_cases::matrix_with_singleton::_just_3_expects ... ok 51 | test test_cases::numeric_values_array::_1_11_expects ... ok 52 | test test_cases::numeric_values_array::_1_12_expects ... ok 53 | test test_cases::numeric_values_array::_2_11_expects ... ok 54 | test test_cases::numeric_values_array::_2_12_expects ... ok 55 | test test_cases::only_singleton::_alone_expects ... ok 56 | test test_cases::str_values_tuple::_one_blue_expects ... ok 57 | test test_cases::str_values_tuple::_one_yellow_expects ... ok 58 | test test_cases::str_values_tuple::_two_blue_expects ... ok 59 | test test_cases::str_values_tuple::_two_yellow_expects ... ok 60 | test test_cases::two_matrices::_1_11_expects ... ok 61 | test test_cases::two_matrices::_1_12_expects ... ok 62 | test test_cases::two_matrices::_2_11_expects ... ok 63 | test test_cases::two_matrices::_2_12_expects ... ok 64 | test test_cases::two_matrices::_3_13_expects ... ok 65 | test test_cases::two_matrices::_3_14_expects ... ok 66 | test test_cases::two_matrices::_4_13_expects ... ok 67 | test test_cases::two_matrices::_4_14_expects ... ok 68 | test test_cases::two_matrices_with_comments::one_two_1_11_expects ... ok 69 | test test_cases::two_matrices_with_comments::one_two_1_12_expects ... ok 70 | test test_cases::two_matrices_with_comments::one_two_2_11_expects ... ok 71 | test test_cases::two_matrices_with_comments::one_two_2_12_expects ... ok 72 | test test_cases::two_matrices_with_comments::three_four_3_13_expects ... ok 73 | test test_cases::two_matrices_with_comments::three_four_3_14_expects ... ok 74 | test test_cases::two_matrices_with_comments::three_four_4_13_expects ... ok 75 | test test_cases::two_matrices_with_comments::three_four_4_14_expects ... ok 76 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__allow_stays_on_fn.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test allow_stays_on_fn::_42_expects ... ok 6 | test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 7 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__cases_can_be_declared_on_async_methods.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 6 | test works_seamlessly_with_async_std::_100i32_expects_100usize ... ok 7 | test works_seamlessly_with_tokio::_100i32_expects_100usize ... ok 8 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__cases_can_be_declared_on_non_test_items.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | error: test failed, to rerun pass `--lib` 6 | test internal_tested_function1::_2_expects_4 ... ok 7 | test internal_tested_function1::_3_expects_6 ... FAILED 8 | test internal_tested_function2::_1_expects_0 ... ok 9 | test internal_tested_function3::_1_expects_matching_3_ ... ok 10 | test internal_tested_function3::_2_expects_inconclusive6 ... ignored 11 | test internal_tested_function4::_2_expects_panicking_some_can_t_ - should panic ... ok 12 | test result: FAILED. 4 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s 13 | thread 'internal_tested_function1::_3_expects_6' panicked at src/lib.rs:8:1: 14 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__cases_can_be_ignored.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test descriptions::_expects_inconclusive_ ... ignored, reason but no comment 6 | test descriptions::ignore_keyword ... ignored, reason and comment 7 | test descriptions::test_is_not_run ... ignored, reason and comment 8 | test ignore_void::_1_expects_inconclusiveempty ... ignored 9 | test ignore_void::_2_expects_inconclusiveempty ... ignored 10 | test inconclusives::_expects_inconclusive_ ... ignored 11 | test inconclusives::ignore_keyword ... ignored 12 | test inconclusives::inconclusive_test ... ignored 13 | test inconclusives::test_is_not_ran ... ignored 14 | test result: ok. 0 passed; 0 failed; 9 ignored; 0 measured; 0 filtered out; finished in 0.00s 15 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__cases_can_panic.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | error: test failed, to rerun pass `--lib` 6 | test panicking::_expects_panicking_some_it_has_to_panic_ - should panic ... ok 7 | test panicking::_expects_panicking_some_this_should_fail_ - should panic ... FAILED 8 | test panics_without_value::_expects_panicking_none - should panic ... ok 9 | test pattern_matching_result_fails::simpleenum_var1_expects_matching_simpleenum_var2_ - should panic ... ok 10 | test result: FAILED. 4 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 11 | test result_which_panics::_2_2_expects_2_3 - should panic ... ok 12 | thread 'panicking::_expects_panicking_some_this_should_fail_' panicked at src/lib.rs:20:5: 13 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__cases_can_return_result.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | error: test failed, to rerun pass `--lib` 6 | test ignore_supported::_12_expects_inconclusivematching_ok_ ... ignored 7 | test is_even::_12_expects ... ok 8 | test is_even::_13_expects ... FAILED 9 | test is_odd_boxed::_12_expects ... FAILED 10 | test is_odd_boxed::_13_expects ... ok 11 | test panics_supported::_12_expects_panicking_none - should panic ... ok 12 | test panics_supported::_13_expects_panicking_some_with_text_ - should panic ... ok 13 | test result: FAILED. 4 passed; 2 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s 14 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__cases_can_use_regex.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | error: incomplete escape sequence, reached end of pattern prematurely 6 | error: test failed, to rerun pass `--lib` 7 | test regex_test::_abcabc201_expects_complex_regex_r_d_ ... ok 8 | test regex_test::_abcabc201_expects_complex_regex_r_d_4_ ... FAILED 9 | test regex_test::_abcabc_expects_complex_regex_r_abc_ ... ok 10 | test regex_test::_kumkwat_expects_complex_regex_r_ ... FAILED 11 | test regex_test::_kumkwat_expects_complex_regex_r_abc_ ... FAILED 12 | test regex_test::_kumkwat_expects_complex_regex_r_kumkwat_ ... ok 13 | test result: FAILED. 3 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 14 | thread 'regex_test::_abcabc201_expects_complex_regex_r_d_4_' panicked at src/lib.rs:5:1: 15 | thread 'regex_test::_kumkwat_expects_complex_regex_r_' panicked at src/lib.rs:5:1: 16 | thread 'regex_test::_kumkwat_expects_complex_regex_r_abc_' panicked at src/lib.rs:5:1: 17 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__cases_declared_on_non_test_items_can_be_used.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | thread 'main' panicked at src/lib.rs:33:5: 6 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__cases_support_basic_features.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 6 | test test_cases::abs_tests::returns_0_for_0 ... ok 7 | test test_cases::abs_tests::returns_given_number_for_positive_input ... ok 8 | test test_cases::abs_tests::returns_opposite_number_for_non_positive_input ... ok 9 | test test_cases::arg_expressions::_2_4_6_to_string_expects ... ok 10 | test test_cases::bar::_expects_string_default_ ... ok 11 | test test_cases::basic_test::_1_expects ... ok 12 | test test_cases::const_in_arg::this_is_desc_not_an_argument ... ok 13 | test test_cases::fancy_addition::some_2_3_some_4_expects_2_3_4 ... ok 14 | test test_cases::fancy_addition::some_2_some_3_expects_5 ... ok 15 | test test_cases::fancy_addition::treats_none_as_0 ... ok 16 | test test_cases::impl_trait::_foo_expects ... ok 17 | test test_cases::keyword_test::true_expects ... ok 18 | test test_cases::leading_underscore_in_test_name::_dummy_expects ... ok 19 | test test_cases::lowercase_test_name::_dummy_code_expects ... ok 20 | test test_cases::multiple_test_cases::_2_expects ... ok 21 | test test_cases::multiple_test_cases::_4_expects ... ok 22 | test test_cases::multiplication_tests::when_both_operands_are_negative ... ok 23 | test test_cases::multiplication_tests::when_both_operands_are_possitive ... ok 24 | test test_cases::multiplication_tests::when_operands_are_swapped ... ok 25 | test test_cases::name::_2nd_test ... ok 26 | test test_cases::name::_4_5_expects ... ok 27 | test test_cases::name::test_1_8_9 ... ok 28 | test test_cases::name::test_3_6_9 ... ok 29 | test test_cases::nested::nested_test_case::_1_1_expects ... ok 30 | test test_cases::nested::using_fn_from_super::_20_22_expects ... ok 31 | test test_cases::nested::using_fn_from_super::_42_expects ... ok 32 | test test_cases::result::_2_expects_4 ... ok 33 | test test_cases::result::_4_expects_8 ... ok 34 | test test_cases::result_and_name::_4_5_expects_9 ... ok 35 | test test_cases::result_and_name::test_no_1 ... ok 36 | test test_cases::result_expresion_with_name::test_result_expression ... ok 37 | test test_cases::result_expression::_2_2_expects_2_2 ... ok 38 | test test_cases::result_with_mod_sep::_42_expects_std_string_string_new_ ... ok 39 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__cases_support_complex_assertions.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | error: test failed, to rerun pass `--lib` 6 | test combinators::_0_3_expects_complex_gt_0_0_and_lt_1_0_or_gt_1_2 ... ok 7 | test combinators::_0_7_expects_complex_gt_0_0_and_lt_1_0_or_gt_1_2 ... ok 8 | test combinators::_1_0_expects_complex_gt_0_0_and_lt_5_0 ... ok 9 | test combinators::_1_0_expects_complex_gt_0_0_or_lt_0_0 ... ok 10 | test combinators::_1_0_expects_complex_gt_0_0_or_lt_1_5_and_lt_2_0 ... ok 11 | test combinators::_2_0_expects_complex_gt_0_0_or_lt_0_0 ... ok 12 | test combinators::_2_0_expects_complex_gt_0_0_or_lt_0_0_and_lt_1_0 ... ok 13 | test combinators_with_arrays::vec_1_2_3_expects_complex_contains_1_and_contains_2_and_contains_in_order_2_3_ ... ok 14 | test combinators_with_arrays::vec_1_2_3_expects_complex_contains_1_and_contains_3_or_contains_5 ... ok 15 | test combinators_with_arrays::vec_1_2_3_expects_complex_contains_1_or_contains_4 ... ok 16 | test combinators_with_arrays::vec_1_2_3_expects_complex_contains_1_or_contains_4_and_contains_2 ... ok 17 | test combinators_with_arrays::vec_1_2_3_expects_complex_contains_6_and_contains_7_or_contains_1 ... ok 18 | test combinators_with_arrays::vec_1_2_3_expects_complex_contains_6_and_contains_7_or_contains_1_and_contains_in_order_1_2_3_ ... ok 19 | test complex_tests::almost_eq1 ... ok 20 | test complex_tests::almost_eq2 ... ok 21 | test complex_tests::eq1 ... ok 22 | test complex_tests::eq2 ... ok 23 | test complex_tests::geq1 ... ok 24 | test complex_tests::geq2 ... ok 25 | test complex_tests::gt1 ... ok 26 | test complex_tests::gt2 ... ok 27 | test complex_tests::leq1 ... ok 28 | test complex_tests::leq2 ... ok 29 | test complex_tests::lt1 ... ok 30 | test complex_tests::lt2 ... ok 31 | test contains_tests::vec_1_2_3_4_expects_complex_contains_1 ... ok 32 | test contains_tests::vec_1_2_3_4_expects_complex_contains_in_order_3_4_ ... ok 33 | test count_general::_abcd_chars_expects_complex_count_4 ... ok 34 | test count_general::std_iter_once_2_expects_complex_count_1 ... ok 35 | test count_general::vec_0_1_3_expects_complex_count_3 ... ok 36 | test count_vec::vec_0_1_3_expects_complex_count_3 ... ok 37 | test count_vec::vec_0_1_expects_complex_count_2 ... ok 38 | test create_path::_cargo_toml_expects_complex_path_path ... ok 39 | test create_path::_src_lib_rs_expects_complex_path_file ... ok 40 | test create_path::long_dir ... ok 41 | test create_path::short_dir ... ok 42 | test empty::vec_0_expects_complex_empty ... FAILED 43 | test empty::vec_expects_complex_empty ... ok 44 | test in_parens::_2_0_expects_complex_eq_2_0 ... ok 45 | test len_byte_str::b_ab_expects_complex_len_2 ... ok 46 | test len_byte_str::b_abc_expects_complex_len_3 ... ok 47 | test len_str::_ab_expects_complex_len_2 ... ok 48 | test len_str::_abc_expects_complex_len_3 ... ok 49 | test len_string::_ab_expects_complex_len_2 ... ok 50 | test len_string::_abc_expects_complex_len_3 ... ok 51 | test len_vec::vec_0_1_3_expects_complex_len_3 ... ok 52 | test len_vec::vec_0_1_expects_complex_len_2 ... ok 53 | test not_complex::_1_0_expects_complex_not_almost_2_1_p_0_01 ... ok 54 | test not_complex::_1_0_expects_complex_not_eq_2_5 ... ok 55 | test not_contains_tests::vec_1_2_3_4_expects_complex_not_contains_5 ... ok 56 | test not_contains_tests::vec_1_2_3_4_expects_complex_not_contains_in_order_3_2_ ... ok 57 | test not_path::_cargo_toml_parse_unwrap_expects_complex_not_path_dir ... ok 58 | test not_path::_cargo_yaml_parse_unwrap_expects_complex_not_path_path ... ok 59 | test not_path::_src_parse_unwrap_expects_complex_not_path_file ... ok 60 | test result: FAILED. 53 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 61 | thread 'empty::vec_0_expects_complex_empty' panicked at src/lib.rs:115:1: 62 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__cases_support_generics.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 6 | test test_generics::source1_expects_1 ... ok 7 | test test_generics::source2_expects_2 ... ok 8 | test test_impl::source1_expects_1 ... ok 9 | test test_impl::source2_expects_2 ... ok 10 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__cases_support_keyword_using.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test power_of_two_with_using::_1_expects_use_assert_is_power_of_two ... ok 6 | test power_of_two_with_using::_2_expects_use_crate_assert_is_power_of_two ... ok 7 | test power_of_two_with_using::_4_expects_use_some_mod_assert_is_power_of_two ... ok 8 | test pretty_assertions_usage::_1_expects_use_wrapped_pretty_assert_1_ ... ok 9 | test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 10 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__cases_support_keyword_with.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test divide_by_zero_f64_with_lambda::_0_0_expects_with_v_f64_assert_v_is_nan_ ... ok 6 | test divide_by_zero_f64_with_lambda::_1_0_expects_with_v_f64_assert_v_is_infinite_ ... ok 7 | test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 8 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__cases_support_multiple_calling_methods.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test can_use_fully_qualified_case_path::_2_expects ... ok 6 | test can_use_fully_qualified_case_path::_3_expects ... ok 7 | test can_use_fully_qualified_test_case_path::first_test ... ok 8 | test can_use_fully_qualified_test_case_path::second_test ... ok 9 | test import::can_import_test_case_attribute::_2_expects ... ok 10 | test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 11 | test short_version::can_use_case_attribute_same_as_test_case::_12u8_expects_12u16 ... ok 12 | test short_version::can_use_case_attribute_same_as_test_case::_8u8_expects_8u16 ... ok 13 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__cases_support_pattern_matching.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | error: test failed, to rerun pass `--lib` 6 | test extended_pattern_matching_result::err_should_fail ... FAILED 7 | test extended_pattern_matching_result::ok_should_fail ... FAILED 8 | test extended_pattern_matching_result::simpleenum_var1_expects_matching_ok_e_e_simpleenum_var1 ... ok 9 | test extended_pattern_matching_result::simpleenum_var2_expects_matching_err_e_e_var2_ ... ok 10 | test pattern_matching_result::simpleenum_var2_expects_matching_simpleenum_var2_ ... ok 11 | test result: FAILED. 3 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 12 | thread 'extended_pattern_matching_result::err_should_fail' panicked at src/lib.rs:16:1: 13 | thread 'extended_pattern_matching_result::ok_should_fail' panicked at src/lib.rs:16:1: 14 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__features_produce_human_readable_errors.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | error: 'with-regex' feature is required to use 'matches-regex' keyword 6 | error: could not compile `features_produce_human_readable_errors` (lib test) due to previous error 7 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__matrices_compilation_errors.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | error: All literal values must be of the same type 6 | error: Range bounds can only be an integer literal 7 | error: Unbounded ranges are not supported 8 | error: could not compile `matrices_compilation_errors` (lib test) due to 5 previous errors 9 | error: number too large to fit in target type 10 | error[E0308]: mismatched types 11 | -------------------------------------------------------------------------------- /tests/snapshots/rust-stable/acceptance__matrices_support_basic_features.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/acceptance_tests.rs 3 | expression: output 4 | --- 5 | test result: ok. 70 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 6 | test test_cases::case_after_matrix::_1_expects ... ok 7 | test test_cases::case_after_matrix::_2_expects ... ok 8 | test test_cases::case_after_matrix::_3_expects ... ok 9 | test test_cases::case_after_matrix::_4_expects ... ok 10 | test test_cases::case_before_matrix::_5_expects ... ok 11 | test test_cases::case_before_matrix::_6_expects ... ok 12 | test test_cases::case_before_matrix::_7_expects ... ok 13 | test test_cases::case_before_matrix::_8_expects ... ok 14 | test test_cases::impl_trait::_bar_expects ... ok 15 | test test_cases::impl_trait::_baz_expects ... ok 16 | test test_cases::impl_trait::_foo_expects ... ok 17 | test test_cases::matrix_with_expressions::_2_2_two_expects ... ok 18 | test test_cases::matrix_with_expressions::_2_4_expects ... ok 19 | test test_cases::matrix_with_expressions::_2_double_2_expects ... ok 20 | test test_cases::matrix_with_keywords::true_false_expects ... ok 21 | test test_cases::matrix_with_keywords::true_true_expects ... ok 22 | test test_cases::matrix_with_panics::_1_11_expects_panicking_some_always_panics_ - should panic ... ok 23 | test test_cases::matrix_with_panics::_1_12_expects_panicking_some_always_panics_ - should panic ... ok 24 | test test_cases::matrix_with_panics::_2_11_expects_panicking_some_always_panics_ - should panic ... ok 25 | test test_cases::matrix_with_panics::_2_12_expects_panicking_some_always_panics_ - should panic ... ok 26 | test test_cases::matrix_with_range::_1_11_expects ... ok 27 | test test_cases::matrix_with_range::_1_12_expects ... ok 28 | test test_cases::matrix_with_range::_2_11_expects ... ok 29 | test test_cases::matrix_with_range::_2_12_expects ... ok 30 | test test_cases::matrix_with_range::_3_11_expects ... ok 31 | test test_cases::matrix_with_range::_3_12_expects ... ok 32 | test test_cases::matrix_with_range::_4_11_expects ... ok 33 | test test_cases::matrix_with_range::_4_12_expects ... ok 34 | test test_cases::matrix_with_range::_5_11_expects ... ok 35 | test test_cases::matrix_with_range::_5_12_expects ... ok 36 | test test_cases::matrix_with_range::_6_11_expects ... ok 37 | test test_cases::matrix_with_range::_6_12_expects ... ok 38 | test test_cases::matrix_with_range::_7_11_expects ... ok 39 | test test_cases::matrix_with_range::_7_12_expects ... ok 40 | test test_cases::matrix_with_range::_8_11_expects ... ok 41 | test test_cases::matrix_with_range::_8_12_expects ... ok 42 | test test_cases::matrix_with_range::_9_11_expects ... ok 43 | test test_cases::matrix_with_range::_9_12_expects ... ok 44 | test test_cases::matrix_with_should_panic::_1_11_expects - should panic ... ok 45 | test test_cases::matrix_with_should_panic::_1_12_expects - should panic ... ok 46 | test test_cases::matrix_with_should_panic::_2_11_expects - should panic ... ok 47 | test test_cases::matrix_with_should_panic::_2_12_expects - should panic ... ok 48 | test test_cases::matrix_with_singleton::_just_1_expects ... ok 49 | test test_cases::matrix_with_singleton::_just_2_expects ... ok 50 | test test_cases::matrix_with_singleton::_just_3_expects ... ok 51 | test test_cases::numeric_values_array::_1_11_expects ... ok 52 | test test_cases::numeric_values_array::_1_12_expects ... ok 53 | test test_cases::numeric_values_array::_2_11_expects ... ok 54 | test test_cases::numeric_values_array::_2_12_expects ... ok 55 | test test_cases::only_singleton::_alone_expects ... ok 56 | test test_cases::str_values_tuple::_one_blue_expects ... ok 57 | test test_cases::str_values_tuple::_one_yellow_expects ... ok 58 | test test_cases::str_values_tuple::_two_blue_expects ... ok 59 | test test_cases::str_values_tuple::_two_yellow_expects ... ok 60 | test test_cases::two_matrices::_1_11_expects ... ok 61 | test test_cases::two_matrices::_1_12_expects ... ok 62 | test test_cases::two_matrices::_2_11_expects ... ok 63 | test test_cases::two_matrices::_2_12_expects ... ok 64 | test test_cases::two_matrices::_3_13_expects ... ok 65 | test test_cases::two_matrices::_3_14_expects ... ok 66 | test test_cases::two_matrices::_4_13_expects ... ok 67 | test test_cases::two_matrices::_4_14_expects ... ok 68 | test test_cases::two_matrices_with_comments::one_two_1_11_expects ... ok 69 | test test_cases::two_matrices_with_comments::one_two_1_12_expects ... ok 70 | test test_cases::two_matrices_with_comments::one_two_2_11_expects ... ok 71 | test test_cases::two_matrices_with_comments::one_two_2_12_expects ... ok 72 | test test_cases::two_matrices_with_comments::three_four_3_13_expects ... ok 73 | test test_cases::two_matrices_with_comments::three_four_3_14_expects ... ok 74 | test test_cases::two_matrices_with_comments::three_four_4_13_expects ... ok 75 | test test_cases::two_matrices_with_comments::three_four_4_14_expects ... ok 76 | --------------------------------------------------------------------------------