├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .vscode ├── settings.json └── tasks.json ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples └── in_not_cfg_test.rs ├── src ├── arbitrary.rs ├── bound.rs ├── lib.rs ├── proptest_fn.rs └── syn_utils.rs └── tests ├── arbitrary.rs ├── compile_fail.rs ├── compile_fail ├── args_not_impl_default.rs ├── args_not_impl_default.stderr ├── cyclic_dependency.rs ├── cyclic_dependency.stderr ├── filter_enum_sharp_val.rs ├── filter_enum_sharp_val.stderr ├── group_span.rs ├── group_span.stderr ├── invalid_filter_field.rs ├── invalid_filter_field.stderr ├── invalid_filter_field_expr.rs ├── invalid_filter_field_expr.stderr ├── invalid_strategy.rs ├── invalid_strategy.stderr ├── invalid_weight.rs ├── invalid_weight.stderr ├── self_dependency.rs ├── self_dependency.stderr ├── sharp_val_not_found_field.rs ├── sharp_val_not_found_field.stderr ├── sharp_val_span.rs ├── sharp_val_span.stderr ├── variant_any_attr.rs ├── variant_any_attr.stderr ├── variant_filter_has_no_fields.rs ├── variant_filter_has_no_fields.stderr ├── variant_strategy_attr.rs ├── variant_strategy_attr.stderr ├── weight_attr_more_arg.rs └── weight_attr_more_arg.stderr ├── proptest_fn.rs ├── readme_arbitrary.rs ├── readme_proptest.rs └── test_helpers.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: [cron: "20 10 * * *"] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Rustup update stable 15 | run: rustup update stable 16 | - name: Show cargo version 17 | run: cargo --version 18 | - name: Build 19 | run: cargo build --verbose 20 | - name: Build tests 21 | run: cargo test --verbose --no-run 22 | - name: Run tests 23 | run: cargo test --verbose 24 | - name: Run compile fail tests 25 | run: cargo test --test compile_fail --verbose -- --ignored 26 | - name: Clippy 27 | run: cargo clippy --all-features --tests --lib -- -W clippy::all 28 | env: 29 | RUSTFLAGS: -D warnings 30 | - name: Rustup toolchain install nightly 31 | run: rustup toolchain install nightly --allow-downgrade --profile minimal 32 | - name: Set minimal versions 33 | run: cargo +nightly update -Z direct-minimal-versions 34 | - name: Build tests (minimal versions) 35 | run: cargo test --verbose --no-run 36 | - name: Run tests (minimal versions) 37 | run: cargo test --verbose 38 | - uses: taiki-e/install-action@cargo-hack 39 | - name: Check msrv 40 | run: cargo hack test --rust-version --workspace --all-targets --ignore-private 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | *.proptest-regressions 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.server.extraEnv": { 3 | "CARGO_TARGET_DIR": "./target/debug-ra", 4 | } 5 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "shell", 8 | "label": "cargo build", 9 | "command": "cargo", 10 | "args": [ 11 | "build" 12 | ], 13 | "problemMatcher": [ 14 | "$rustc" 15 | ], 16 | "presentation": { 17 | "panel": "dedicated", 18 | "clear": true 19 | } 20 | // "options": { 21 | // "env": { 22 | // "RUST_BACKTRACE": "1", 23 | // } 24 | // } 25 | }, 26 | { 27 | "type": "shell", 28 | "label": "cargo build release", 29 | "command": "cargo", 30 | "args": [ 31 | "build", 32 | "--release" 33 | ], 34 | "problemMatcher": [ 35 | "$rustc" 36 | ], 37 | "presentation": { 38 | "panel": "dedicated", 39 | "clear": true 40 | } 41 | }, 42 | { 43 | "type": "shell", 44 | "label": "cargo watch", 45 | "command": "wt", 46 | "args": [ 47 | "new-tab", 48 | "cargo-watch", 49 | "-c", 50 | "-x", 51 | "test" 52 | ], 53 | "problemMatcher": [], 54 | "presentation": { 55 | "panel": "dedicated", 56 | "clear": true 57 | } 58 | }, 59 | { 60 | "type": "shell", 61 | "label": "cargo test", 62 | "command": "cargo", 63 | "args": [ 64 | "test" 65 | ], 66 | "problemMatcher": [ 67 | "$rustc" 68 | ], 69 | "group": { 70 | "kind": "build", 71 | "isDefault": true 72 | }, 73 | "presentation": { 74 | "panel": "dedicated", 75 | "clear": true 76 | }, 77 | "dependsOn": [ 78 | "rustdoc-include" 79 | ], 80 | }, 81 | { 82 | "type": "shell", 83 | "label": "cargo test 32bit windows", 84 | "command": "cargo test --target-dir ./target/i686-debug --target i686-pc-windows-msvc ", 85 | "problemMatcher": [ 86 | "$rustc" 87 | ], 88 | "presentation": { 89 | "panel": "dedicated", 90 | "clear": true 91 | }, 92 | }, 93 | { 94 | "type": "shell", 95 | "label": "cargo test nightly", 96 | "command": "cargo", 97 | "args": [ 98 | "+nightly", 99 | "test", 100 | "--target-dir", 101 | "target/debug-nightly", 102 | ], 103 | "problemMatcher": [ 104 | "$rustc" 105 | ], 106 | "group": { 107 | "kind": "test", 108 | "isDefault": true 109 | }, 110 | "presentation": { 111 | "panel": "dedicated", 112 | "clear": true 113 | }, 114 | "options": { 115 | "env": { 116 | "RUSTFLAGS": "-Z proc-macro-backtrace" 117 | } 118 | }, 119 | }, 120 | { 121 | "type": "shell", 122 | "label": "cargo run exmaple", 123 | "command": "cargo", 124 | "args": [ 125 | "run", 126 | "--example", 127 | "${fileBasenameNoExtension}" 128 | ], 129 | "problemMatcher": [ 130 | "$rustc" 131 | ], 132 | "presentation": { 133 | "panel": "dedicated", 134 | "clear": true 135 | } 136 | }, 137 | { 138 | "type": "shell", 139 | "label": "cargo doc open", 140 | "command": "cargo", 141 | "args": [ 142 | "doc", 143 | "--open", 144 | "--no-deps", 145 | "--all-features" 146 | ], 147 | "problemMatcher": [ 148 | "$rustc" 149 | ], 150 | "presentation": { 151 | "panel": "dedicated", 152 | "clear": true 153 | }, 154 | "dependsOn": [ 155 | "rustdoc-include" 156 | ], 157 | }, 158 | { 159 | "type": "shell", 160 | "label": "cargo llvm-cov open", 161 | "command": "cargo", 162 | "args": [ 163 | "llvm-cov", 164 | "--open", 165 | ], 166 | "problemMatcher": [ 167 | "$rustc" 168 | ], 169 | "presentation": { 170 | "panel": "dedicated", 171 | "clear": true 172 | }, 173 | }, 174 | { 175 | "type": "shell", 176 | "label": "cargo clippy", 177 | "command": "cargo", 178 | "args": [ 179 | "clippy", 180 | "--all-features", 181 | "--tests", 182 | "--lib", 183 | "--", 184 | "-W", 185 | "clippy::all" 186 | ], 187 | "problemMatcher": [ 188 | "$rustc" 189 | ], 190 | "presentation": { 191 | "panel": "dedicated", 192 | "clear": true 193 | } 194 | }, 195 | { 196 | "type": "shell", 197 | "label": "cargo clippy nightly", 198 | "command": "cargo", 199 | "args": [ 200 | "+nightly", 201 | "clippy", 202 | "--target-dir", 203 | "target/debug-nightly", 204 | "--all-features", 205 | "--tests", 206 | "--lib", 207 | "--", 208 | "-W", 209 | "clippy::all" 210 | ], 211 | "problemMatcher": [ 212 | "$rustc" 213 | ], 214 | "presentation": { 215 | "panel": "dedicated", 216 | "clear": true 217 | } 218 | }, 219 | { 220 | "type": "shell", 221 | "label": "cargo fix & fmt", 222 | "command": "cargo fix --all && cargo clippy --fix --allow-dirty --all && cargo fmt --all", 223 | "problemMatcher": [ 224 | "$rustc" 225 | ], 226 | }, 227 | { 228 | "type": "shell", 229 | "label": "cargo bench", 230 | "command": "cargo", 231 | "args": [ 232 | "bench" 233 | ], 234 | "options": { 235 | "cwd": "${workspaceFolder}/benchmarks" 236 | }, 237 | "problemMatcher": [ 238 | "$rustc" 239 | ], 240 | "presentation": { 241 | "panel": "dedicated", 242 | "clear": true 243 | } 244 | }, 245 | { 246 | "type": "shell", 247 | "label": "cargo update minimal-versions", 248 | "command": "cargo", 249 | "args": [ 250 | "+nightly", 251 | "update", 252 | "-Z", 253 | "minimal-versions" 254 | ], 255 | "problemMatcher": [ 256 | "$rustc" 257 | ], 258 | "presentation": { 259 | "panel": "dedicated", 260 | "clear": true 261 | } 262 | }, 263 | { 264 | "type": "shell", 265 | "label": "update compile error", 266 | "command": "cargo", 267 | "args": [ 268 | "test", 269 | "--test", 270 | "compile_fail", 271 | "--", 272 | "--ignored" 273 | ], 274 | "problemMatcher": [ 275 | "$rustc" 276 | ], 277 | "presentation": { 278 | "panel": "dedicated", 279 | "clear": true 280 | }, 281 | "options": { 282 | "env": { 283 | "TRYBUILD": "overwrite" 284 | } 285 | } 286 | }, 287 | { 288 | "type": "shell", 289 | "label": "check msrv", 290 | "command": "cargo hack test --rust-version --workspace --all-targets --ignore-private", 291 | "problemMatcher": [ 292 | "$rustc" 293 | ], 294 | "presentation": { 295 | "panel": "dedicated", 296 | "clear": true 297 | }, 298 | }, 299 | { 300 | "type": "shell", 301 | "label": "rustdoc-include", 302 | "command": "rustdoc-include", 303 | "args": [ 304 | "--root", 305 | "${workspaceFolder}" 306 | ], 307 | "problemMatcher": [ 308 | { 309 | "owner": "rustdoc-include", 310 | "fileLocation": [ 311 | "relative", 312 | "${workspaceFolder}" 313 | ], 314 | "pattern": [ 315 | { 316 | "regexp": "^(error): (.*)$", 317 | "severity": 1, 318 | "message": 2, 319 | }, 320 | { 321 | "regexp": "^--> (.*):(\\d+)\\s*$", 322 | "file": 1, 323 | "line": 2, 324 | "loop": true, 325 | }, 326 | ] 327 | }, 328 | ], 329 | "presentation": { 330 | "panel": "dedicated", 331 | "clear": true 332 | } 333 | }, 334 | ] 335 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-strategy" 3 | version = "0.4.1" 4 | authors = ["frozenlib"] 5 | license = "MIT OR Apache-2.0" 6 | readme = "README.md" 7 | repository = "https://github.com/frozenlib/test-strategy" 8 | documentation = "https://docs.rs/test-strategy/" 9 | keywords = ["macro", "testing", "proptest"] 10 | categories = ["development-tools::testing"] 11 | description = "Procedural macro to easily write higher-order strategies in proptest." 12 | edition = "2021" 13 | rust-version = "1.70.0" 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [lib] 18 | proc-macro = true 19 | 20 | [dependencies] 21 | syn = { version = "2.0.100", features = ["visit", "full", "extra-traits"] } 22 | quote = "1.0.40" 23 | proc-macro2 = "1.0.94" 24 | structmeta = "0.3.0" 25 | 26 | [dev-dependencies] 27 | proptest = "1.6.0" 28 | trybuild = "1.0.104" 29 | tokio = { version = "1.44.1", features = ["rt-multi-thread"] } 30 | anyhow = "1.0.97" 31 | googletest = "0.14.0" 32 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 FrozenLib 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 | # test-strategy 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/test-strategy.svg)][test-strategy] 4 | [![Docs.rs](https://docs.rs/test-strategy/badge.svg)](https://docs.rs/test-strategy/) 5 | [![Actions Status](https://github.com/frozenlib/test-strategy/workflows/CI/badge.svg)](https://github.com/frozenlib/test-strategy/actions) 6 | 7 | This crate provides two procedural macros, `#[derive(Arbitrary)]` and `#[proptest]`. 8 | 9 | Each of these macros is an alternative to the following proptest's official macros. 10 | 11 | | [test-strategy][] | [proptest][] | [proptest-derive][] | 12 | | ------------------------------------------ | ------------------------------ | ------------------------------------ | 13 | | [`#[derive(Arbitrary)]`](#derivearbitrary) | | [`#[derive(Arbitrary)]`][official-a] | 14 | | [`#[proptest]`](#proptest) | [`proptest ! { }`][official-m] | | 15 | 16 | [test-strategy]: https://crates.io/crates/test-strategy 17 | [proptest]: https://crates.io/crates/proptest 18 | [proptest-derive]: https://crates.io/crates/proptest-derive 19 | [official-m]: https://altsysrq.github.io/rustdoc/proptest/latest/proptest/macro.proptest.html 20 | [official-a]: https://altsysrq.github.io/proptest-book/proptest-derive/modifiers.html 21 | 22 | The macros provided by this crate have the following advantages over the proptest's official macros. 23 | 24 | - Supports higher-order strategies. (`#[derive(Arbitrary)]` and `#[proptest]`) 25 | - Code formatting is not disabled. (`#[proptest]`) 26 | 27 | However, the syntax of this crate's macros are not compatible with the syntax of the official macros. 28 | 29 | ## Install 30 | 31 | Add this to your Cargo.toml: 32 | 33 | ```toml 34 | [dependencies] 35 | test-strategy = "0.4.1" 36 | proptest = "1.6.0" 37 | ``` 38 | 39 | ## Example 40 | 41 | You can use `#[derive(Arbitrary)]` to automatically implement proptest's `Arbitrary` trait. 42 | 43 | ```rust 44 | use test_strategy::Arbitrary; 45 | 46 | #[derive(Arbitrary, Debug)] 47 | struct TestInputStruct { 48 | x: u32, 49 | 50 | #[strategy(1..10u32)] 51 | y: u32, 52 | 53 | #[strategy(0..#y)] 54 | z: u32, 55 | } 56 | 57 | #[derive(Arbitrary, Debug)] 58 | enum TestInputEnum { 59 | A, 60 | B, 61 | #[weight(3)] 62 | C, 63 | X(u32), 64 | Y(#[strategy(0..10u32)] u32), 65 | } 66 | ``` 67 | 68 | You can define a property test by adding `#[proptest]` to the function. 69 | 70 | ```rust 71 | use test_strategy::proptest; 72 | 73 | #[proptest] 74 | fn my_test(_x: u32, #[strategy(1..10u32)] y: u32, #[strategy(0..#y)] z: u32) { 75 | assert!(1 <= y && y < 10); 76 | assert!(z <= y); 77 | } 78 | ``` 79 | 80 | ## Attributes 81 | 82 | Attributes can be written in the following positions. 83 | 84 | | attribute | function | struct | enum | variant | field | function parameter | 85 | | --------------------------------------------------- | -------- | ------ | ---- | ------- | ----- | ------------------ | 86 | | [`#[strategy]`](#strategy) | | | | | ✔ | ✔ | 87 | | [`#[any]`](#any) | | | | | ✔ | ✔ | 88 | | [`#[weight]`](#weight) | | | | ✔ | | | 89 | | [`#[map]`](#map) | | | | | ✔ | ✔ | 90 | | [`#[filter]`](#filter) | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | 91 | | [`#[by_ref]`](#by_ref) | | | | | ✔ | ✔ | 92 | | [`#[arbitrary(args = T)]`](#arbitraryargs--t) | | ✔ | ✔ | | | | 93 | | [`#[arbitrary(bound(...))]`](#arbitraryboundt1-t2-) | | ✔ | ✔ | ✔ | ✔ | | 94 | | [`#[arbitrary(dump)]`](#arbitrarydump) | | ✔ | ✔ | | | | 95 | | [`#[proptest]`](#proptest) | ✔ | | | | | | 96 | | [`#[proptest(async = ...)]`](#proptestasync--) | ✔ | | | | | | 97 | | [`#[proptest(dump)]`](#proptestdump) | ✔ | | | | | | 98 | 99 | ## `#[derive(Arbitrary)]` 100 | 101 | You can implement `proptest::arbitrary::Arbitrary` automatically by adding `#[derive(Arbitrary)]` to struct or enum declaration. 102 | 103 | By default, all fields are set using the strategy obtained by `proptest::arbitrary::any()`. 104 | 105 | So the following two codes are equivalent. 106 | 107 | ```rust 108 | use test_strategy::Arbitrary; 109 | 110 | #[derive(Arbitrary, Debug)] 111 | struct TestInput { 112 | x: u32, 113 | y: u32, 114 | } 115 | ``` 116 | 117 | ```rust 118 | use proptest::{ 119 | arbitrary::{any, Arbitrary}, 120 | strategy::{BoxedStrategy, Strategy}, 121 | }; 122 | 123 | #[derive(Debug)] 124 | struct TestInput { 125 | x: u32, 126 | y: u32, 127 | } 128 | impl Arbitrary for TestInput { 129 | type Parameters = (); 130 | type Strategy = BoxedStrategy; 131 | 132 | fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { 133 | let x = any::(); 134 | let y = any::(); 135 | (x, y).prop_map(|(x, y)| Self { x, y }).boxed() 136 | } 137 | } 138 | ``` 139 | 140 | ## `#[strategy]` 141 | 142 | You can specify a strategy to generate values for the field by adding `#[strategy(...)]` to the field. 143 | 144 | In the following example, the value of field `x` will be less than 20. 145 | 146 | ```rust 147 | use test_strategy::Arbitrary; 148 | 149 | #[derive(Arbitrary, Debug)] 150 | struct TestInput { 151 | #[strategy(0..20u32)] 152 | x: u32, 153 | } 154 | ``` 155 | 156 | In `#[strategy]`, the values of other fields can be used by following `#` to the name of the field. 157 | 158 | In the following example, the value of `y` is less than or equal to `x`. 159 | 160 | ```rust 161 | use test_strategy::Arbitrary; 162 | 163 | #[derive(Arbitrary, Debug)] 164 | struct TestInput { 165 | x: u32, 166 | #[strategy(0..=#x)] 167 | y: u32, 168 | } 169 | ``` 170 | 171 | ## `#[any]` 172 | 173 | Instead of writing `#[strategy(any_with::(expr))]`, you can write `#[any(expr)]`. 174 | 175 | ```rust 176 | use proptest::collection::size_range; 177 | use test_strategy::Arbitrary; 178 | 179 | #[derive(Arbitrary, Debug, PartialEq)] 180 | struct TestInput { 181 | #[any(size_range(0..16).lift())] 182 | x: Vec, 183 | } 184 | ``` 185 | 186 | Instead of writing an expression to be passed to `any_with`, you can write only the value of the field to be changed from the default value. 187 | 188 | Therefore, the following `TestInputA`, `TestInputB` and `TestInputC` are equivalent. 189 | 190 | ```rust 191 | use test_strategy::Arbitrary; 192 | 193 | #[derive(Arbitrary, Debug)] 194 | struct TestInputA { 195 | #[any(InnerArgs { upper : 20, ..InnerArgs::default() })] 196 | a: Inner, 197 | } 198 | #[derive(Arbitrary, Debug)] 199 | struct TestInputB { 200 | #[any(InnerArgs::default(), upper = 20)] 201 | a: Inner, 202 | } 203 | #[derive(Arbitrary, Debug)] 204 | struct TestInputC { 205 | #[any(upper = 20)] 206 | a: Inner, 207 | } 208 | 209 | #[derive(Default)] 210 | struct InnerArgs { 211 | lower: i32, 212 | upper: i32, 213 | } 214 | 215 | #[derive(Arbitrary, Debug)] 216 | #[arbitrary(args = InnerArgs)] 217 | struct Inner { 218 | #[strategy(args.lower..args.upper)] 219 | x: i32, 220 | } 221 | ``` 222 | 223 | ## `#[weight]` 224 | 225 | By default, all variants appear with equal probability. 226 | 227 | You can add `#[weight]` to the variant to change the probability of the variant appearing. 228 | 229 | In the following example, `TestInput::B` is twice as likely to appear as `TestInput::A`. 230 | 231 | ```rust 232 | use test_strategy::Arbitrary; 233 | 234 | #[derive(Arbitrary, Debug)] 235 | enum TestInput { 236 | A, 237 | 238 | #[weight(2)] 239 | B, 240 | } 241 | ``` 242 | 243 | If you add `#[weight(0)]` to a variant, the variant does not appear, so you can use a type in that variant that cannot be used as `Arbitrary`. 244 | 245 | ```rust 246 | use test_strategy::Arbitrary; 247 | 248 | #[derive(Debug)] 249 | struct NotArbitrary; 250 | 251 | #[derive(Arbitrary, Debug)] 252 | enum TestInput { 253 | A, 254 | 255 | #[allow(dead_code)] 256 | #[weight(0)] // Removing this `#[weight(0)]` will cause a compile error. 257 | B(NotArbitrary), 258 | } 259 | ``` 260 | 261 | ## `#[map]` 262 | 263 | Instead of using `prop_map` in `#[strategy(...)]`, `#[map(...)]` can be used. 264 | 265 | The following codes mean the same thing. 266 | 267 | ```rust 268 | use proptest::arbitrary::any; 269 | use proptest::strategy::Strategy; 270 | use test_strategy::Arbitrary; 271 | 272 | #[derive(Arbitrary, Debug)] 273 | struct TestInput1 { 274 | #[strategy(any::().prop_map(|x| x + 1))] 275 | x: u32, 276 | } 277 | 278 | #[derive(Arbitrary, Debug)] 279 | struct TestInput2 { 280 | #[strategy(any::())] 281 | #[map(|x| x + 1)] 282 | x: u32, 283 | } 284 | 285 | #[derive(Arbitrary, Debug)] 286 | struct TestInput3 { 287 | #[map(|x: u32| x + 1)] 288 | x: u32, 289 | } 290 | ``` 291 | 292 | References to other fields in the function applied to `prop_map` or `#[map(...)]` will generate different strategies. 293 | 294 | Referencing another field in `#[strategy(...)]` will expand it to `prop_flat_map`, even if it is in `prop_map`. 295 | 296 | ```rust 297 | use proptest::arbitrary::any; 298 | use proptest::strategy::{Just, Strategy}; 299 | use test_strategy::Arbitrary; 300 | 301 | #[derive(Arbitrary, Debug)] 302 | struct T1 { 303 | x: u32, 304 | 305 | #[strategy(any::().prop_map(move |y| #x + y))] 306 | y: u32, 307 | } 308 | // The code above generates the following strategy. 309 | let t1 = any::() 310 | .prop_flat_map(|x| (Just(x), any::().prop_map(move |y| x + y))) 311 | .prop_map(|(x, y)| T1 { x, y }); 312 | ``` 313 | 314 | On the other hand, if you refer to another field in `#[map]`, it will expand to `prop_map`. 315 | 316 | ```rust 317 | use proptest::arbitrary::any; 318 | use proptest::strategy::Strategy; 319 | use test_strategy::Arbitrary; 320 | 321 | #[derive(Arbitrary, Debug)] 322 | struct T2 { 323 | x: u32, 324 | 325 | #[map(|y: u32| #x + y)] 326 | y: u32, 327 | } 328 | // The code above generates the following strategy. 329 | let t2 = (any::(), any::()).prop_map(|(x, y)| T2 { x, y }); 330 | ``` 331 | 332 | If the input and output types of the function specified in `#[map]` are different, the value type of the strategy set in `#[strategy]` is the type of the function's input, not the type of the field. 333 | 334 | ```rust 335 | use proptest::arbitrary::any; 336 | use proptest::sample::Index; 337 | use test_strategy::Arbitrary; 338 | 339 | #[derive(Arbitrary, Debug)] 340 | struct T1 { 341 | #[strategy(any::())] 342 | #[map(|i: Index| i.index(10))] 343 | x: usize, 344 | } 345 | 346 | // `#[strategy(any::())]` can be omitted. 347 | #[derive(Arbitrary, Debug)] 348 | struct T2 { 349 | #[map(|i: Index| i.index(10))] 350 | x: usize, 351 | } 352 | ``` 353 | 354 | ## `#[filter]` 355 | 356 | By adding `#[filter]` , you can limit the values generated. 357 | 358 | In the following examples, x is an even number. 359 | 360 | ```rust 361 | use test_strategy::Arbitrary; 362 | 363 | #[derive(Arbitrary, Debug)] 364 | struct TestInput { 365 | #[filter(#x % 2 == 0)] 366 | x: u32, 367 | } 368 | ``` 369 | 370 | You can also use multiple variables in a predicate. 371 | 372 | ```rust 373 | use test_strategy::Arbitrary; 374 | 375 | #[derive(Arbitrary, Debug)] 376 | #[filter((#x + #y) % 2 == 0)] 377 | struct T1 { 378 | x: u32, 379 | y: u32, 380 | } 381 | 382 | #[derive(Arbitrary, Debug)] 383 | struct T2 { 384 | x: u32, 385 | #[filter((#x + #y) % 2 == 0)] 386 | y: u32, 387 | } 388 | ``` 389 | 390 | You can use the value of a structure or enum in the filter by using `#self`. 391 | 392 | ```rust 393 | use test_strategy::Arbitrary; 394 | 395 | #[derive(Arbitrary, Debug)] 396 | #[filter((#self.x + #self.y) % 2 == 0)] 397 | struct TestInput { 398 | x: u32, 399 | y: u32, 400 | } 401 | ``` 402 | 403 | If the expression specified for `#[filter]` does not contain a variable named by appending # to its own field name, the expression is treated as a predicate function, rather than an expression that returns a bool. 404 | 405 | ```rust 406 | use test_strategy::Arbitrary; 407 | 408 | #[derive(Arbitrary, Debug)] 409 | struct TestInput { 410 | #[filter(is_even)] 411 | x: u32, 412 | } 413 | fn is_even(x: &u32) -> bool { 414 | x % 2 == 0 415 | } 416 | 417 | #[derive(Arbitrary, Debug)] 418 | struct T2 { 419 | a: u32, 420 | 421 | // Since `#a` exists but `#b` does not, it is treated as a predicate function. 422 | #[filter(|&x| x > #a)] 423 | b: u32, 424 | } 425 | ``` 426 | 427 | Similarly, an expression that does not contain `#self` in the `#[filter(...)]` that it attaches to a type is treated as a predicate function. 428 | 429 | ```rust 430 | use test_strategy::Arbitrary; 431 | 432 | #[derive(Arbitrary, Debug)] 433 | #[filter(is_even)] 434 | struct T { 435 | x: u32, 436 | } 437 | fn is_even(t: &T) -> bool { 438 | t.x % 2 == 0 439 | } 440 | ``` 441 | 442 | You can specify a filter name by passing two arguments to `#[filter]`. 443 | 444 | ```rust 445 | use test_strategy::Arbitrary; 446 | 447 | #[derive(Arbitrary, Debug)] 448 | struct TestInput { 449 | #[filter("x is even", #x % 2 == 0)] 450 | x: u32, 451 | } 452 | ``` 453 | 454 | ## `#[by_ref]` 455 | 456 | By default, if you use a variable with `#[strategy]`, `#[any]`, `#[map]` or `#[filter]` with `#` attached to it, the cloned value is set. 457 | 458 | Adding `#[by_ref]` to the field makes it use the reference instead of the cloned value. 459 | 460 | ```rust 461 | use test_strategy::Arbitrary; 462 | 463 | #[derive(Arbitrary, Debug)] 464 | struct TestInput { 465 | #[by_ref] 466 | #[strategy(1..10u32)] 467 | x: u32, 468 | 469 | #[strategy(0..*#x)] 470 | y: u32, 471 | } 472 | ``` 473 | 474 | ## `#[arbitrary]` 475 | 476 | ### `#[arbitrary(args = T)]` 477 | 478 | Specifies the type of `Arbitrary::Parameters`. 479 | 480 | You can use the `Rc` value of this type in `#[strategy]`, `#[any]`, or `#[filter]` with the variable name `args`. 481 | 482 | ```rust 483 | use test_strategy::Arbitrary; 484 | 485 | #[derive(Debug, Default)] 486 | struct TestInputArgs { 487 | x_max: u32, 488 | } 489 | 490 | #[derive(Arbitrary, Debug)] 491 | #[arbitrary(args = TestInputArgs)] 492 | struct TestInput { 493 | #[strategy(0..=args.x_max)] 494 | x: u32, 495 | } 496 | ``` 497 | 498 | ### `#[arbitrary(bound(T1, T2, ..))]` 499 | 500 | By default, if the type of field for which `#[strategy]` is not specified contains a generic parameter, that type is set to trait bounds. 501 | 502 | Therefore, the following `TestInputA` and `TestInputB` are equivalent. 503 | 504 | ```rust 505 | use proptest::{ 506 | arbitrary::any, arbitrary::Arbitrary, strategy::BoxedStrategy, strategy::Strategy, 507 | }; 508 | use test_strategy::Arbitrary; 509 | 510 | #[derive(Arbitrary, Debug)] 511 | struct TestInputA { 512 | x: T, 513 | } 514 | 515 | #[derive(Debug)] 516 | struct TestInputB { 517 | x: T, 518 | } 519 | impl Arbitrary for TestInputB { 520 | type Parameters = (); 521 | type Strategy = BoxedStrategy; 522 | 523 | fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { 524 | any::().prop_map(|x| Self { x }).boxed() 525 | } 526 | } 527 | ``` 528 | 529 | Types of fields with `#[strategy]` do not set trait bounds automatically, so you need to set trait bound manually with `#[arbitrary(bound(T))]`. 530 | 531 | ```rust 532 | use proptest::arbitrary::any_with; 533 | use test_strategy::Arbitrary; 534 | 535 | #[derive(Arbitrary, Debug, PartialEq)] 536 | #[arbitrary(bound(T))] 537 | struct TestInput { 538 | #[strategy(any_with::(Default::default()))] 539 | x: T, 540 | } 541 | ``` 542 | 543 | You can also specify where predicate instead of type. 544 | 545 | ```rust 546 | use proptest::arbitrary::{any_with, Arbitrary}; 547 | use test_strategy::Arbitrary; 548 | 549 | #[derive(Arbitrary, Debug, PartialEq)] 550 | #[arbitrary(bound(T : Arbitrary + 'static))] 551 | struct TestInput { 552 | #[strategy(any_with::(Default::default()))] 553 | x: T, 554 | } 555 | ``` 556 | 557 | `..` means automatically generated trait bounds. 558 | 559 | The following example uses a manually specified trait bounds in addition to the automatically generated trait bounds. 560 | 561 | ```rust 562 | use proptest::arbitrary::any_with; 563 | use test_strategy::Arbitrary; 564 | 565 | #[derive(Arbitrary, Debug, PartialEq)] 566 | #[arbitrary(bound(T1, ..))] 567 | struct TestInput { 568 | #[strategy(any_with::(Default::default()))] 569 | x: T1, 570 | 571 | y: T2, 572 | } 573 | ``` 574 | 575 | ### `#[arbitrary(dump)]` 576 | 577 | Causes a compile error and outputs the code generated by `#[derive(Arbitrary)]` as an error message. 578 | 579 | ## `#[proptest]` 580 | 581 | `#[proptest]` is the attribute used instead of `#[test]` when defining a property test. 582 | 583 | The following example defines a test that takes a variety of integers as input. 584 | 585 | ```rust 586 | use test_strategy::proptest; 587 | 588 | #[proptest] 589 | fn my_test(_input: i32) { 590 | // ... 591 | } 592 | ``` 593 | 594 | You can add `#[strategy]`, `#[any]`, `#[filter]`, `#[by_ref]` to the parameter of the function with `# [proptest]`. 595 | 596 | ```rust 597 | use test_strategy::proptest; 598 | 599 | #[proptest] 600 | fn my_test2(#[strategy(10..20)] _input: i32) { 601 | // ... 602 | } 603 | ``` 604 | 605 | You can change the configuration of a property test by setting the argument of `#[proptest]` attribute to a value of [`proptest::prelude::ProptestConfig`](https://docs.rs/proptest/latest/proptest/prelude/index.html#reexport.ProptestConfig) type. 606 | 607 | ```rust 608 | use proptest::prelude::ProptestConfig; 609 | use test_strategy::proptest; 610 | 611 | #[proptest(ProptestConfig { cases : 1000, ..ProptestConfig::default() })] 612 | fn my_test_with_config(_input: i32) { 613 | // ... 614 | } 615 | ``` 616 | 617 | As with `#[any]`, you can also set only the value of the field to be changed from the default value. 618 | 619 | The example below is equivalent to the one above. 620 | 621 | ```rust 622 | use proptest::prelude::ProptestConfig; 623 | use test_strategy::proptest; 624 | 625 | #[proptest(ProptestConfig::default(), cases = 1000)] 626 | fn my_test_with_config_2(_input: i32) { 627 | // ... 628 | } 629 | 630 | #[proptest(cases = 1000)] 631 | fn my_test_with_config_3(_input: i32) { 632 | // ... 633 | } 634 | ``` 635 | 636 | ### `#[proptest(async = ...)]` 637 | 638 | Async functions can be tested by setting `async = ...` to the argument of `#[proptest]`. 639 | 640 | The following values are allowed after `async =`. 641 | The value specifies the asynchronous runtime used for the test. 642 | 643 | - "tokio" 644 | 645 | ```toml 646 | [dev-dependencies] 647 | test-strategy = "0.4.1" 648 | proptest = "1.6.0" 649 | tokio = { version = "1.38.0", features = ["rt-multi-thread"] } 650 | ``` 651 | 652 | ```rust 653 | use test_strategy::proptest; 654 | use proptest::prop_assert; 655 | 656 | #[proptest(async = "tokio")] 657 | async fn my_test_async() { 658 | async { }.await; 659 | prop_assert!(true); 660 | } 661 | ``` 662 | 663 | ### `#[proptest(dump)]` 664 | 665 | You can use `#[proptest(dump)]` and output the code generated by `#[proptest]` as an compile error message. 666 | 667 | ```compile_fail 668 | #[proptest(dump)] 669 | fn my_test(_input: i32) { 670 | // ... 671 | } 672 | ``` 673 | 674 | ## License 675 | 676 | This project is dual licensed under Apache-2.0/MIT. See the two LICENSE-\* files for details. 677 | 678 | ## Contribution 679 | 680 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 681 | -------------------------------------------------------------------------------- /examples/in_not_cfg_test.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | 3 | #[test_strategy::proptest] 4 | fn arg(x: u8) {} 5 | 6 | #[test_strategy::proptest(cases = 100)] 7 | fn arg_and_config(x: u8) {} 8 | -------------------------------------------------------------------------------- /src/arbitrary.rs: -------------------------------------------------------------------------------- 1 | use crate::syn_utils::{ 2 | impl_trait_result, span_in_args, to_valid_ident, Arg, Args, FieldKey, GenericParamSet, 3 | SharpVals, 4 | }; 5 | use crate::{bound::*, syn_utils::parse_from_attrs}; 6 | use proc_macro2::{Span, TokenStream}; 7 | use quote::{quote, quote_spanned, ToTokens}; 8 | use std::collections::BTreeMap; 9 | use std::{collections::HashMap, fmt::Write, mem::take}; 10 | use structmeta::*; 11 | use syn::{ 12 | parse_quote, parse_str, spanned::Spanned, Attribute, Data, DataEnum, DataStruct, DeriveInput, 13 | Expr, Fields, Ident, Lit, Member, Path, Result, Type, 14 | }; 15 | use syn::{parse_quote_spanned, Meta, Pat}; 16 | 17 | pub fn derive_arbitrary(input: DeriveInput) -> Result { 18 | let args: ArbitraryArgsForType = parse_from_attrs(&input.attrs, "arbitrary")?; 19 | let type_parameters = if let Some(ty) = &args.args { 20 | quote_spanned!(ty.span()=> type Parameters = #ty;) 21 | } else { 22 | quote! { 23 | type Parameters = (); 24 | } 25 | }; 26 | let mut bounds = Bounds::from_data(args.bound); 27 | let expr = match &input.data { 28 | Data::Struct(data) => expr_for_struct(&input, data, &mut bounds)?, 29 | Data::Enum(data) => expr_for_enum(&input, data, &mut bounds)?, 30 | _ => bail!( 31 | input.span(), 32 | "`#[derive(Arbitrary)]` supports only enum and struct." 33 | ), 34 | }; 35 | impl_trait_result( 36 | &input, 37 | &parse_quote!(proptest::arbitrary::Arbitrary), 38 | &bounds.build_wheres(quote!(proptest::arbitrary::Arbitrary + 'static)), 39 | quote! { 40 | #type_parameters 41 | type Strategy = proptest::strategy::BoxedStrategy; 42 | #[allow(clippy::redundant_closure_call)] 43 | fn arbitrary_with(args: ::Parameters) -> Self::Strategy { 44 | #[allow(dead_code)] 45 | fn _filter_fn(f: impl Fn(&T) -> bool) -> impl Fn(&T) -> bool { 46 | f 47 | } 48 | #[allow(dead_code)] 49 | fn _filter_fn_once(f: impl FnOnce(&T) -> bool) -> impl FnOnce(&T) -> bool { 50 | f 51 | } 52 | #[allow(dead_code)] 53 | fn _map_to_any U>( 54 | _: impl Fn() -> F, 55 | ) -> impl proptest::strategy::Strategy { 56 | proptest::arbitrary::any::() 57 | } 58 | 59 | #[allow(dead_code)] 60 | fn _map_to_any_with_init U>( 61 | _: impl Fn() -> F, 62 | p: ::Parameters, 63 | ) -> ::Parameters { 64 | p 65 | } 66 | 67 | #[allow(dead_code)] 68 | fn _map_to_any_with U>( 69 | _: impl Fn() -> F, 70 | args: ::Parameters, 71 | ) -> impl proptest::strategy::Strategy { 72 | proptest::arbitrary::any_with::(args) 73 | } 74 | 75 | #[allow(unused_variables)] 76 | let args = std::rc::Rc::new(args); 77 | proptest::strategy::Strategy::boxed(#expr) 78 | } 79 | }, 80 | args.dump.value(), 81 | ) 82 | } 83 | fn expr_for_struct( 84 | input: &DeriveInput, 85 | data: &DataStruct, 86 | bounds: &mut Bounds, 87 | ) -> Result { 88 | let generics = GenericParamSet::new(&input.generics); 89 | expr_for_fields( 90 | parse_quote!(Self), 91 | &generics, 92 | &data.fields, 93 | &input.attrs, 94 | true, 95 | bounds, 96 | ) 97 | } 98 | fn expr_for_enum(input: &DeriveInput, data: &DataEnum, bounds: &mut Bounds) -> Result { 99 | if data.variants.is_empty() { 100 | bail!(Span::call_site(), "zero variant enum was not supported."); 101 | } 102 | let generics = GenericParamSet::new(&input.generics); 103 | let mut exprs = Vec::new(); 104 | for variant in &data.variants { 105 | let args: ArbitraryArgsForFieldOrVariant = parse_from_attrs(&variant.attrs, "arbitrary")?; 106 | let mut weight = None; 107 | for attr in &variant.attrs { 108 | let Some(ident) = attr.path().get_ident() else { 109 | continue; 110 | }; 111 | if ident == "weight" { 112 | if weight.is_some() { 113 | bail!(attr.span(), "`#[weight]` can specify only once."); 114 | } 115 | weight = Some(attr.parse_args::()?); 116 | } 117 | if ident == "any" || ident == "strategy" || ident == "map" || ident == "by_ref" { 118 | bail!( 119 | attr.span(), 120 | "`#[{ident}]` cannot be specified for a variant. Consider specifying it for a field instead." 121 | ); 122 | } 123 | } 124 | let weight = if let Some(arg) = weight { 125 | if arg.is_zero() { 126 | continue; 127 | } else { 128 | let expr = arg.0; 129 | quote_spanned!(expr.span()=> _to_weight(#expr)) 130 | } 131 | } else { 132 | quote!(1) 133 | }; 134 | let variant_ident = &variant.ident; 135 | let mut bounds = bounds.child(args.bound); 136 | let expr = expr_for_fields( 137 | parse_quote!(Self::#variant_ident), 138 | &generics, 139 | &variant.fields, 140 | &variant.attrs, 141 | false, 142 | &mut bounds, 143 | )?; 144 | exprs.push(quote! {#weight=> #expr}); 145 | } 146 | let s: Ident = parse_quote!(_s); 147 | let filter_lets = Filter::from_enum_attrs_make_let(&input.attrs, &s)?; 148 | Ok(quote! { 149 | { 150 | #[allow(dead_code)] 151 | fn _to_weight(weight: u32) -> u32 { weight } 152 | let #s = proptest::prop_oneof![#(#exprs,)*]; 153 | #filter_lets 154 | #s 155 | } 156 | }) 157 | } 158 | fn expr_for_fields( 159 | self_path: Path, 160 | generics: &GenericParamSet, 161 | fields: &Fields, 162 | attrs: &[Attribute], 163 | filter_allow_fn: bool, 164 | bounds: &mut Bounds, 165 | ) -> Result { 166 | let b = StrategyBuilder::from_fields(self_path, fields, attrs, filter_allow_fn)?; 167 | b.get_bound_types(generics, bounds)?; 168 | b.build() 169 | } 170 | 171 | #[derive(StructMeta)] 172 | struct WeightArg(Expr); 173 | impl WeightArg { 174 | fn is_zero(&self) -> bool { 175 | if let Expr::Lit(lit) = &self.0 { 176 | if let Lit::Int(lit) = &lit.lit { 177 | if let Ok(value) = lit.base10_parse::() { 178 | return value == 0; 179 | } 180 | } 181 | } 182 | false 183 | } 184 | } 185 | 186 | #[derive(StructMeta)] 187 | struct AnyArgs { 188 | #[struct_meta(unnamed)] 189 | initializer: Option, 190 | setters: HashMap>, 191 | } 192 | impl Default for AnyArgs { 193 | fn default() -> Self { 194 | Self::empty() 195 | } 196 | } 197 | 198 | impl AnyArgs { 199 | fn empty() -> Self { 200 | Self { 201 | initializer: None, 202 | setters: HashMap::new(), 203 | } 204 | } 205 | fn into_strategy(self, ty: &StrategyValueType) -> TokenStream { 206 | if self.initializer.is_none() && self.setters.is_empty() { 207 | ty.any() 208 | } else { 209 | let init = self 210 | .initializer 211 | .unwrap_or_else(|| parse_quote!(std::default::Default::default())) 212 | .to_token_stream(); 213 | if self.setters.is_empty() { 214 | ty.any_with(init) 215 | } else { 216 | let mut setters: Vec<_> = self.setters.into_iter().collect(); 217 | setters.sort_by(|v0, v1| v0.0.cmp(&v1.0)); 218 | let setters = setters.into_iter().map(|(name, expr)| { 219 | let member = Member::Named(to_valid_ident(&name).unwrap()); 220 | let expr = &expr.value; 221 | quote!(_any_args.#member = #expr;) 222 | }); 223 | let any_with = ty.any_with(quote!(_any_args)); 224 | let any_with_args_let = ty.any_with_args_let(quote!(_any_args), init); 225 | quote! { 226 | { 227 | #any_with_args_let; 228 | #(#setters)* 229 | #any_with 230 | } 231 | } 232 | } 233 | } 234 | } 235 | } 236 | 237 | #[derive(StructMeta, Default)] 238 | struct ArbitraryArgsForType { 239 | args: Option, 240 | bound: Option>, 241 | dump: Flag, 242 | } 243 | 244 | #[derive(StructMeta, Default)] 245 | struct ArbitraryArgsForFieldOrVariant { 246 | bound: Option>, 247 | } 248 | 249 | #[derive(Clone)] 250 | struct Filter { 251 | whence: Expr, 252 | expr: Expr, 253 | } 254 | impl Filter { 255 | fn parse(span: Span, args: Args) -> Result { 256 | let mut values = Vec::new(); 257 | for arg in args { 258 | match arg { 259 | Arg::Value(value) => { 260 | values.push(value); 261 | } 262 | Arg::NameValue { .. } => bail!(arg.span(), "named argument was not supported."), 263 | } 264 | } 265 | let whence; 266 | let expr; 267 | match values.len() { 268 | 1 => { 269 | let mut iter = values.into_iter(); 270 | expr = iter.next().unwrap(); 271 | let whence_str = expr.to_token_stream().to_string(); 272 | whence = parse_quote!(#whence_str); 273 | } 274 | 2 => { 275 | let mut iter = values.into_iter(); 276 | whence = iter.next().unwrap(); 277 | expr = iter.next().unwrap(); 278 | } 279 | _ => bail!( 280 | span, 281 | "expected `#[filter(whence, fun)]` or `#[filter(fun)]`." 282 | ), 283 | } 284 | Ok(Self { whence, expr }) 285 | } 286 | fn from_enum_attrs_make_let(attrs: &[Attribute], var: &Ident) -> Result { 287 | let mut results = TokenStream::new(); 288 | for attr in attrs { 289 | if attr.path().is_ident("filter") { 290 | let mut sharp_vals = SharpVals::new(false, true); 291 | let mut filter = Filter::parse(attr.span(), sharp_vals.expand_args(&attr.meta)?)?; 292 | if sharp_vals.self_span.is_none() { 293 | filter = filter.with_fn_arg_self(); 294 | } 295 | results.extend(filter.make_let_as_expr(var, "e!(_self), "e!())); 296 | } 297 | } 298 | Ok(results) 299 | } 300 | fn with_fn_arg(&self, arg: &Ident, arg_by_ref: bool, arg_ty: &Type) -> Self { 301 | let span = self.expr.span(); 302 | let expr = &self.expr; 303 | let arg = if arg_by_ref { 304 | quote!(#arg) 305 | } else { 306 | quote!(&#arg) 307 | }; 308 | let expr = parse_quote_spanned!(span=> (_filter_fn_once::<#arg_ty>(#expr))(#arg)); 309 | Self { 310 | expr, 311 | whence: self.whence.clone(), 312 | } 313 | } 314 | fn with_fn_arg_self(&self) -> Self { 315 | self.with_fn_arg(&parse_quote!(_self), true, &parse_quote!(Self)) 316 | } 317 | 318 | fn make_let_as_expr(&self, var: &Ident, ps: &TokenStream, lets: &TokenStream) -> TokenStream { 319 | let expr = self.expr.to_token_stream(); 320 | Self::make_let_with(var, &self.whence, ps, lets, expr) 321 | } 322 | fn make_let_as_fn(&self, var: &Ident, arg_ty: &Type) -> TokenStream { 323 | let span = self.expr.span(); 324 | let expr = &self.expr; 325 | let fun = parse_quote_spanned!(span=> _filter_fn::<#arg_ty>(#expr)); 326 | Self::make_let_with_fun(var, &self.whence, fun) 327 | } 328 | 329 | fn make_let_with( 330 | var: &Ident, 331 | whence: &Expr, 332 | ps: &TokenStream, 333 | lets: &TokenStream, 334 | expr: TokenStream, 335 | ) -> TokenStream { 336 | let fun = quote_spanned! {expr.span()=> move |#ps| { 337 | #lets 338 | #expr 339 | }}; 340 | Self::make_let_with_fun(var, whence, fun) 341 | } 342 | fn make_let_with_fun(var: &Ident, whence: &Expr, fun: TokenStream) -> TokenStream { 343 | quote_spanned! {fun.span()=> 344 | let #var = { 345 | #[allow(unused_variables)] 346 | let args = as std::clone::Clone>::clone(&args); 347 | proptest::strategy::Strategy::prop_filter(#var, #whence, #fun) 348 | }; 349 | } 350 | } 351 | } 352 | 353 | #[derive(Clone)] 354 | struct UnaryFilter { 355 | filter: Filter, 356 | arg_exists: bool, 357 | arg: Ident, 358 | arg_by_ref: bool, 359 | arg_ty: Type, 360 | } 361 | impl UnaryFilter { 362 | fn make_let_expr(&self, var: &Ident, ps: &TokenStream, lets: &TokenStream) -> TokenStream { 363 | self.to_expr_filter().make_let_as_expr(var, ps, lets) 364 | } 365 | fn make_let_fn(&self, var: &Ident) -> TokenStream { 366 | if self.arg_exists { 367 | let arg = &self.arg; 368 | let arg_ty = &self.arg_ty; 369 | let lets = if self.arg_by_ref { 370 | quote!() 371 | } else { 372 | quote!(let #arg = <#arg_ty as std::clone::Clone>::clone(#arg);) 373 | }; 374 | self.filter.make_let_as_expr(var, "e!(#arg), &lets) 375 | } else { 376 | self.filter.make_let_as_fn(var, &self.arg_ty) 377 | } 378 | } 379 | fn to_expr_filter(&self) -> Filter { 380 | if self.arg_exists { 381 | self.filter.clone() 382 | } else { 383 | self.filter 384 | .with_fn_arg(&self.arg, self.arg_by_ref, &self.arg_ty) 385 | } 386 | } 387 | } 388 | 389 | struct FieldsFilter { 390 | filter: Filter, 391 | _vals: Vec, 392 | } 393 | 394 | struct StrategyBuilder { 395 | ts: TokenStream, 396 | items: Vec, 397 | self_path: Path, 398 | fields: Fields, 399 | filters_fields: Vec, 400 | filters_self: Vec, 401 | } 402 | 403 | impl StrategyBuilder { 404 | fn from_fields( 405 | self_path: Path, 406 | fields: &Fields, 407 | attrs: &[Attribute], 408 | filter_allow_self: bool, 409 | ) -> Result { 410 | let mut ts = TokenStream::new(); 411 | let mut fs = Vec::new(); 412 | let mut by_refs = Vec::new(); 413 | let mut key_to_idx = HashMap::new(); 414 | for (idx, field) in fields.iter().enumerate() { 415 | key_to_idx.insert(FieldKey::from_field(idx, field), idx); 416 | fs.push(field); 417 | let mut by_ref = false; 418 | for attr in &field.attrs { 419 | if attr.path().is_ident("by_ref") { 420 | by_ref = true; 421 | match &attr.meta { 422 | Meta::Path(_) => {} 423 | _ => { 424 | let span = span_in_args(&attr.meta); 425 | bail!(span, "Arguments are not allowed."); 426 | } 427 | } 428 | } 429 | } 430 | by_refs.push(by_ref); 431 | } 432 | let mut items_field = Vec::new(); 433 | let mut items_other = Vec::new(); 434 | let mut filters_fields = Vec::new(); 435 | 436 | for (idx, field) in fields.iter().enumerate() { 437 | let key = FieldKey::from_field(idx, field); 438 | let mut expr_strategy = None::; 439 | let mut expr_map = None::; 440 | let mut filters_field = Vec::new(); 441 | let mut sharp_vals_strategy = SharpVals::new(true, false); 442 | let mut sharp_vals_map = SharpVals::new(true, false); 443 | let by_ref = by_refs[idx]; 444 | let mut is_any = false; 445 | let mut strategy_value_type = StrategyValueType::Type(field.ty.clone()); 446 | for attr in &field.attrs { 447 | if attr.path().is_ident("map") { 448 | if expr_map.is_some() { 449 | bail!(attr.span(), "`#[map]` can be specified only once."); 450 | } 451 | let args: Args = sharp_vals_map.expand_args_or_default(&attr.meta)?; 452 | let expr = args.expect_single_value(attr.span())?; 453 | let ty = &field.ty; 454 | let input = key.to_dummy_ident(); 455 | expr_map = Some(StrategyExpr::new( 456 | quote_spanned!(expr.span()=> #ty), 457 | quote_spanned!(expr.span()=> (#expr)(#input)), 458 | true, 459 | )); 460 | if let Some(ty) = input_type(expr) { 461 | strategy_value_type = StrategyValueType::Type(ty.clone()); 462 | } else { 463 | let dependency_map = to_idxs(&sharp_vals_map.vals, &key_to_idx)?; 464 | let mut lets = Vec::new(); 465 | for idx in dependency_map { 466 | let field = &fs[idx]; 467 | let key = FieldKey::from_field(idx, field); 468 | let ident = key.to_dummy_ident(); 469 | let ty = &field.ty; 470 | let ty = if by_refs[idx] { 471 | quote!(&#ty) 472 | } else { 473 | quote!(#ty) 474 | }; 475 | lets.push(quote!(let #ident : #ty = unreachable!();)) 476 | } 477 | strategy_value_type = StrategyValueType::Map(quote! { || { 478 | #[allow(clippy::diverging_sub_expression)] 479 | #[allow(unreachable_code)] 480 | { 481 | #(#lets)* 482 | #expr 483 | } 484 | }}); 485 | } 486 | } 487 | } 488 | for attr in &field.attrs { 489 | let is_strategy_attr = attr.path().is_ident("strategy"); 490 | let is_any_attr = attr.path().is_ident("any"); 491 | if expr_strategy.is_some() && (is_strategy_attr || is_any_attr) { 492 | bail!( 493 | attr.span(), 494 | "`#[any]` and `#[strategy]` can only be specified once in total." 495 | ); 496 | } 497 | if is_strategy_attr { 498 | let args: Args = sharp_vals_strategy.expand_args_or_default(&attr.meta)?; 499 | let expr = args.expect_single_value(attr.span())?; 500 | let ty = strategy_value_type.get(); 501 | let func_ident = Ident::new(&format!("_strategy_of_{key}"), expr.span()); 502 | ts.extend(quote_spanned! {ty.span()=> 503 | #[allow(dead_code)] 504 | #[allow(non_snake_case)] 505 | fn #func_ident>(s: S) -> impl proptest::strategy::Strategy { s } 506 | }); 507 | expr_strategy = Some(StrategyExpr::new( 508 | quote!(_), 509 | quote_spanned!(expr.span()=> #func_ident::<#ty, _>( 510 | { 511 | #[allow(unused_variables)] 512 | let args = std::ops::Deref::deref(&args); 513 | #expr 514 | })), 515 | false, 516 | )); 517 | } 518 | if is_any_attr { 519 | is_any = true; 520 | let any_attr: AnyArgs = 521 | sharp_vals_strategy.expand_args_or_default(&attr.meta)?; 522 | expr_strategy = Some(StrategyExpr::new( 523 | quote!(), 524 | any_attr.into_strategy(&strategy_value_type), 525 | false, 526 | )); 527 | } 528 | } 529 | for attr in &field.attrs { 530 | if attr.path().is_ident("filter") { 531 | let mut sharp_vals = SharpVals::new(true, false); 532 | let filter = Filter::parse(attr.span(), sharp_vals.expand_args(&attr.meta)?)?; 533 | let arg = key.to_dummy_ident(); 534 | let uf = UnaryFilter { 535 | filter, 536 | arg_exists: sharp_vals.vals.contains_key(&key), 537 | arg_by_ref: by_ref, 538 | arg_ty: field.ty.clone(), 539 | arg, 540 | }; 541 | if sharp_vals.vals.is_empty() || (uf.arg_exists && sharp_vals.vals.len() == 1) { 542 | filters_field.push(uf); 543 | } else { 544 | let mut vals = to_idxs(&sharp_vals.vals, &key_to_idx)?; 545 | if !uf.arg_exists { 546 | vals.push(idx); 547 | vals.sort_unstable(); 548 | } 549 | let filter = uf.to_expr_filter(); 550 | filters_fields.push(FieldsFilter { 551 | filter, 552 | _vals: vals, 553 | }); 554 | } 555 | } 556 | } 557 | let mut expr_strategy = if let Some(expr_strategy) = expr_strategy { 558 | expr_strategy 559 | } else { 560 | is_any = true; 561 | let ty = strategy_value_type.get(); 562 | StrategyExpr::new( 563 | quote!(#ty), 564 | AnyArgs::empty().into_strategy(&strategy_value_type), 565 | false, 566 | ) 567 | }; 568 | let dependency_strategy = to_idxs(&sharp_vals_strategy.vals, &key_to_idx)?; 569 | let arbitrary_type = if is_any { 570 | if let StrategyValueType::Type(ty) = strategy_value_type { 571 | Some(ty) 572 | } else { 573 | None 574 | } 575 | } else { 576 | None 577 | }; 578 | if let Some(mut expr_map) = expr_map { 579 | let base_idx = fields.len() + items_other.len(); 580 | let mut dependency_map = to_idxs(&sharp_vals_map.vals, &key_to_idx)?; 581 | dependency_map.push(base_idx); 582 | expr_map.filters = filters_field; 583 | items_field.push(StrategyItem::new( 584 | idx, 585 | key.clone(), 586 | by_ref, 587 | true, 588 | Some(base_idx), 589 | arbitrary_type, 590 | expr_map, 591 | dependency_map, 592 | )); 593 | items_other.push(StrategyItem::new( 594 | base_idx, 595 | key, 596 | false, 597 | false, 598 | None, 599 | None, 600 | expr_strategy, 601 | dependency_strategy, 602 | )); 603 | } else { 604 | expr_strategy.filters = filters_field; 605 | items_field.push(StrategyItem::new( 606 | idx, 607 | key, 608 | by_ref, 609 | true, 610 | None, 611 | arbitrary_type, 612 | expr_strategy, 613 | dependency_strategy, 614 | )); 615 | } 616 | } 617 | let mut filters_self = Vec::new(); 618 | for attr in attrs { 619 | if attr.path().is_ident("filter") { 620 | let mut sharp_vals = SharpVals::new(true, filter_allow_self); 621 | let mut filter = Filter::parse(attr.span(), sharp_vals.expand_args(&attr.meta)?)?; 622 | if !sharp_vals.vals.is_empty() { 623 | let vals = to_idxs(&sharp_vals.vals, &key_to_idx)?; 624 | filters_fields.push(FieldsFilter { 625 | filter, 626 | _vals: vals, 627 | }); 628 | } else if filter_allow_self { 629 | if sharp_vals.self_span.is_none() { 630 | filter = filter.with_fn_arg_self(); 631 | } 632 | filters_self.push(filter); 633 | } else { 634 | let span = span_in_args(&attr.meta); 635 | bail!(span, "Filters that reference `self` in the variant (filters with no reference to the field) cannot be set.") 636 | } 637 | } 638 | } 639 | let fields = fields.clone(); 640 | items_field.extend(items_other); 641 | Ok(Self { 642 | ts, 643 | items: items_field, 644 | self_path, 645 | fields, 646 | filters_fields, 647 | filters_self, 648 | }) 649 | } 650 | fn get_bound_types(&self, generics: &GenericParamSet, bounds: &mut Bounds) -> Result<()> { 651 | if !bounds.can_extend { 652 | return Ok(()); 653 | } 654 | for (idx, field) in self.fields.iter().enumerate() { 655 | let args: ArbitraryArgsForFieldOrVariant = parse_from_attrs(&field.attrs, "arbitrary")?; 656 | let mut bounds = bounds.child(args.bound); 657 | if bounds.can_extend { 658 | if let Some(ty) = &self.items[idx].arbitrary_type { 659 | if generics.contains_in_type(ty) { 660 | bounds.ty.push(ty.clone()); 661 | } 662 | } 663 | } 664 | } 665 | Ok(()) 666 | } 667 | fn build(mut self) -> Result { 668 | if self.items.is_empty() { 669 | let constructor = build_constructor(&self.self_path, &self.fields, quote!()); 670 | return Ok(quote! { proptest::strategy::LazyJust::new(|| #constructor ) }); 671 | } 672 | for item in &mut self.items { 673 | item.try_create_independent_strategy(&mut self.ts); 674 | } 675 | while !self.is_exists_all_fields() { 676 | if !self.try_create_dependent_strategy() { 677 | let mut cd_str = String::new(); 678 | let cd_idxs = self.get_cyclic_dependency().unwrap(); 679 | for &cd_idx in &cd_idxs { 680 | let item = &self.items[cd_idx]; 681 | if item.is_field { 682 | write!(&mut cd_str, "{} -> ", &item.key).unwrap(); 683 | } 684 | } 685 | for &cd_idx in &cd_idxs { 686 | let item = &self.items[cd_idx]; 687 | if item.is_field { 688 | write!(&mut cd_str, "{}", &item.key).unwrap(); 689 | break; 690 | } 691 | } 692 | bail!(Span::call_site(), "found cyclic dependency. ({0})", cd_str); 693 | } 694 | } 695 | self.merge_all_groups(); 696 | let var = &self.items[0].strategy_ident(); 697 | let ps = self.pat_group_vars(0); 698 | for filter in &self.filters_fields { 699 | let lets = self.let_group_vars(0, true); 700 | self.ts 701 | .extend(filter.filter.make_let_as_expr(var, &ps, &lets)); 702 | } 703 | 704 | let mut args = Vec::new(); 705 | for idx in 0..self.fields.len() { 706 | let key = &self.items[idx].key; 707 | let value = key.to_dummy_ident(); 708 | args.push(if let Some(key) = key.to_valid_ident() { 709 | quote!(#key : #value) 710 | } else { 711 | quote!(#value) 712 | }); 713 | } 714 | let constructor = build_constructor(&self.self_path, &self.fields, quote!(#(#args, )*)); 715 | self.ts.extend(quote! { 716 | let #var = proptest::strategy::Strategy::prop_map(#var, |#ps| #constructor); 717 | }); 718 | for filter in &self.filters_self { 719 | self.ts 720 | .extend(filter.make_let_as_expr(var, "e!(_self), "e!())); 721 | } 722 | self.ts.extend(quote!(#var)); 723 | let ts = self.ts; 724 | Ok(quote! { { #ts } }) 725 | } 726 | fn try_create_dependent_strategy(&mut self) -> bool { 727 | let mut created = false; 728 | for idx in 0..self.items.len() { 729 | if self.is_exists(idx) { 730 | continue; 731 | } 732 | let group_next = self.items[idx] 733 | .dependency 734 | .iter() 735 | .map(|&idx| self.resolve_group_next_input(idx)) 736 | .min() 737 | .unwrap_or(None); 738 | if let Some(group_next) = group_next { 739 | self.set_group_next_new(idx, group_next); 740 | created = true; 741 | } 742 | } 743 | for idx in 0..self.items.len() { 744 | if let Some(group_next) = self.resolve_group_next_input(idx) { 745 | self.set_group_next(idx, group_next); 746 | } 747 | } 748 | for idx in 0..self.items.len() { 749 | self.register_group_dependency(idx); 750 | } 751 | for idx in 0..self.items.len() { 752 | if !self.is_exists(idx) { 753 | self.register_group_next_items(idx); 754 | } 755 | } 756 | for idx in 0..self.items.len() { 757 | if self.is_exists(idx) { 758 | self.register_group_next_items(idx); 759 | } 760 | } 761 | for idx in 0..self.items.len() { 762 | if self.is_group_next_new(idx) { 763 | let mut inputs = Vec::new(); 764 | let mut ps = Vec::new(); 765 | let mut exprs = Vec::new(); 766 | for &input_idx in &self.items[idx].group_dependency { 767 | inputs.push(self.items[input_idx].strategy_ident()); 768 | ps.push(self.pat_group_vars(input_idx)); 769 | } 770 | let inputs = cons_tuple(&inputs); 771 | let ps = cons_tuple(&ps); 772 | let ps_next = self.pat_group_next_vars(idx); 773 | let var = Ident::new("_s", Span::call_site()); 774 | let lets = self.let_group_vars(idx, true); 775 | let mut filter_lets = TokenStream::new(); 776 | for &group_item_next in &self.items[idx].group_items_next { 777 | let mut expr = self.strategy_expr(group_item_next); 778 | for filter in &expr.filters { 779 | filter_lets.extend(filter.make_let_expr(&var, &ps_next, &lets)); 780 | } 781 | expr.filters.clear(); 782 | exprs.push(expr); 783 | } 784 | let ident = self.items[idx].strategy_ident(); 785 | let exprs = if exprs.iter().all(|e| e.is_jast) { 786 | let exprs: Vec<_> = exprs.iter().map(|e| &e.expr).collect(); 787 | let exprs = cons_tuple(&exprs); 788 | quote! { 789 | { 790 | let #var = proptest::strategy::Strategy::prop_map((#inputs), { 791 | #[allow(unused_variables)] 792 | let args = as std::clone::Clone>::clone(&args); 793 | move |#ps| #exprs 794 | }); 795 | #filter_lets 796 | #var 797 | } 798 | } 799 | } else { 800 | let exprs = cons_tuple(&exprs); 801 | quote! { 802 | { 803 | let #var = proptest::strategy::Strategy::prop_flat_map(#inputs, { 804 | #[allow(unused_variables)] 805 | let args = as std::clone::Clone>::clone(&args); 806 | move |#ps| #exprs 807 | }); 808 | #filter_lets 809 | #var 810 | } 811 | } 812 | }; 813 | self.ts.extend(quote! { 814 | let #ident = { 815 | #[allow(unused_variables)] 816 | let args = as std::clone::Clone>::clone(&args); 817 | #exprs 818 | }; 819 | }); 820 | } 821 | } 822 | for idx in 0..self.items.len() { 823 | self.items[idx].group = self.items[idx].group_next; 824 | self.items[idx].offset = self.items[idx].offset_next.take(); 825 | self.items[idx].group_offset = None; 826 | self.items[idx].group_items = take(&mut self.items[idx].group_items_next); 827 | self.items[idx].group_dependency.clear(); 828 | } 829 | created 830 | } 831 | fn resolve_group_next_input(&self, idx: usize) -> Option { 832 | let item_ref = &self.items[idx]; 833 | item_ref.group?; 834 | let group_next = item_ref.group_next?; 835 | if group_next == idx { 836 | return Some(idx); 837 | } 838 | self.resolve_group_next_input(group_next) 839 | } 840 | fn set_group_next_new(&mut self, idx: usize, group_next: usize) { 841 | if let Some(base_idx) = self.items[idx].base_idx { 842 | self.items[base_idx].is_dropped = true; 843 | } 844 | self.set_group_next(idx, group_next); 845 | for i in 0..self.items[idx].dependency.len() { 846 | self.set_group_next(self.items[idx].dependency[i], group_next) 847 | } 848 | } 849 | fn set_group_next(&mut self, group: usize, group_next: usize) { 850 | let group_next_old = self.items[group].group_next; 851 | if group_next_old == Some(group_next) { 852 | return; 853 | } 854 | if let Some(group_next_old) = group_next_old { 855 | if group_next_old != group { 856 | self.set_group_next(group_next_old, group_next) 857 | } 858 | } 859 | self.items[group].group_next = Some(group_next); 860 | } 861 | fn merge_all_groups(&mut self) { 862 | for idx in 0..self.items.len() { 863 | self.items[idx].group_next = Some(0); 864 | } 865 | self.merge_groups(); 866 | } 867 | fn merge_groups(&mut self) { 868 | for idx in 0..self.items.len() { 869 | self.items[idx].group_next = Some(0); 870 | } 871 | for idx in 0..self.items.len() { 872 | self.register_group_dependency(idx); 873 | self.register_group_next_items(idx); 874 | } 875 | for idx in 0..self.items.len() { 876 | if self.is_group_next_new(idx) { 877 | let mut inputs = Vec::new(); 878 | let mut ps = Vec::new(); 879 | let mut exprs = Vec::new(); 880 | for &input_idx in &self.items[idx].group_dependency { 881 | inputs.push(self.items[input_idx].strategy_ident()); 882 | ps.push(self.pat_group_vars(input_idx)); 883 | } 884 | for &group_item_next in &self.items[idx].group_items_next { 885 | exprs.push(self.items[group_item_next].key.to_dummy_ident()); 886 | } 887 | let var = self.items[idx].strategy_ident(); 888 | let inputs = cons_tuple(&inputs); 889 | let ps = cons_tuple(&ps); 890 | let exprs = cons_tuple(&exprs); 891 | self.ts.extend(quote! { 892 | let #var = proptest::strategy::Strategy::prop_map(#inputs, |#ps| #exprs); 893 | }); 894 | } 895 | } 896 | for idx in 0..self.items.len() { 897 | self.items[idx].group = self.items[idx].group_next; 898 | self.items[idx].offset = self.items[idx].offset_next.take(); 899 | self.items[idx].group_offset = None; 900 | self.items[idx].group_items = take(&mut self.items[idx].group_items_next); 901 | self.items[idx].group_dependency.clear(); 902 | } 903 | } 904 | fn register_group_dependency(&mut self, idx: usize) { 905 | if let Some(group_next) = self.items[idx].group_next { 906 | if self.is_group(idx) { 907 | self.items[idx].group_offset = Some(self.items[group_next].group_dependency.len()); 908 | self.items[group_next].group_dependency.push(idx); 909 | } 910 | } 911 | } 912 | fn register_group_next_items(&mut self, idx: usize) { 913 | let item = &self.items[idx]; 914 | if !item.is_dropped { 915 | if let Some(group_next) = item.group_next { 916 | debug_assert!(item.offset_next.is_none()); 917 | self.items[idx].offset_next = Some(self.items[group_next].group_items_next.len()); 918 | self.items[group_next].group_items_next.push(idx); 919 | } 920 | } 921 | } 922 | fn get_cyclic_dependency(&self) -> Option> { 923 | let mut results = Vec::new(); 924 | let mut to_offset = HashMap::new(); 925 | if self.get_cyclic_dependency_impl(0, &mut results, &mut to_offset) { 926 | Some(results) 927 | } else { 928 | None 929 | } 930 | } 931 | fn get_cyclic_dependency_impl( 932 | &self, 933 | idx: usize, 934 | results: &mut Vec, 935 | to_offset: &mut HashMap, 936 | ) -> bool { 937 | if let Some(&offset) = to_offset.get(&idx) { 938 | results.drain(0..offset); 939 | return true; 940 | } 941 | 942 | to_offset.insert(idx, results.len()); 943 | results.push(idx); 944 | for &dep in &self.items[idx].dependency { 945 | if self.get_cyclic_dependency_impl(dep, results, to_offset) { 946 | return true; 947 | } 948 | } 949 | results.pop(); 950 | to_offset.remove(&idx); 951 | false 952 | } 953 | 954 | fn is_exists_all_fields(&self) -> bool { 955 | (0..self.fields.len()).all(|idx| self.is_exists(idx)) 956 | } 957 | fn is_exists(&self, idx: usize) -> bool { 958 | let item = &self.items[idx]; 959 | item.group.is_some() || item.is_dropped 960 | } 961 | fn is_group(&self, idx: usize) -> bool { 962 | self.items[idx].group == Some(idx) 963 | } 964 | fn is_group_next(&self, idx: usize) -> bool { 965 | self.items[idx].group_next == Some(idx) 966 | } 967 | fn is_group_next_new(&self, idx: usize) -> bool { 968 | let item = &self.items[idx]; 969 | item.group_next == Some(idx) && item.group_items_next != item.group_items 970 | } 971 | fn pat_group_vars(&self, idx: usize) -> TokenStream { 972 | assert!(self.is_group(idx)); 973 | let mut ps = Vec::new(); 974 | for &idx in &self.items[idx].group_items { 975 | ps.push(self.items[idx].key.to_dummy_ident()); 976 | } 977 | cons_tuple(&ps) 978 | } 979 | fn pat_group_next_vars(&self, idx: usize) -> TokenStream { 980 | assert!(self.is_group_next(idx)); 981 | let mut ps = Vec::new(); 982 | for &idx in &self.items[idx].group_items_next { 983 | ps.push(self.items[idx].key.to_dummy_ident()); 984 | } 985 | cons_tuple(&ps) 986 | } 987 | fn let_group_vars(&self, idx: usize, from_ref: bool) -> TokenStream { 988 | let mut lets = Vec::new(); 989 | for &idx in &self.items[idx].group_items { 990 | lets.push(self.items[idx].let_sharp_val(from_ref)); 991 | } 992 | quote!(#(#lets)*) 993 | } 994 | fn strategy_expr(&self, idx: usize) -> StrategyExpr { 995 | if self.is_exists(idx) { 996 | let ident = self.items[idx].key.to_dummy_ident(); 997 | StrategyExpr::new(quote!(_), quote!(std::clone::Clone::clone(&#ident)), true) 998 | } else { 999 | let item = &self.items[idx]; 1000 | let mut lets = Vec::new(); 1001 | for &dep in &item.dependency { 1002 | lets.push(self.items[dep].let_sharp_val(false)); 1003 | } 1004 | lets.push(quote! { 1005 | #[allow(unused_variables)] 1006 | let args = std::ops::Deref::deref(&args); 1007 | }); 1008 | let expr = &item.expr.expr; 1009 | StrategyExpr { 1010 | expr: quote! { 1011 | { 1012 | #(#lets)* 1013 | #expr 1014 | } 1015 | }, 1016 | filters: item.expr.filters.clone(), 1017 | ty: item.expr.ty.clone(), 1018 | is_jast: item.expr.is_jast, 1019 | } 1020 | } 1021 | } 1022 | } 1023 | 1024 | struct StrategyItem { 1025 | idx: usize, 1026 | key: FieldKey, 1027 | by_ref: bool, 1028 | is_field: bool, 1029 | base_idx: Option, 1030 | arbitrary_type: Option, 1031 | expr: StrategyExpr, 1032 | dependency: Vec, 1033 | 1034 | is_dropped: bool, 1035 | 1036 | group: Option, 1037 | group_next: Option, 1038 | 1039 | offset: Option, 1040 | offset_next: Option, 1041 | 1042 | group_items: Vec, 1043 | group_items_next: Vec, 1044 | group_dependency: Vec, 1045 | group_offset: Option, 1046 | } 1047 | 1048 | impl StrategyItem { 1049 | #[allow(clippy::too_many_arguments)] 1050 | fn new( 1051 | idx: usize, 1052 | key: FieldKey, 1053 | by_ref: bool, 1054 | is_field: bool, 1055 | base_idx: Option, 1056 | arbitrary_type: Option, 1057 | expr: StrategyExpr, 1058 | dependency: Vec, 1059 | ) -> Self { 1060 | Self { 1061 | idx, 1062 | key, 1063 | by_ref, 1064 | is_field, 1065 | base_idx, 1066 | arbitrary_type, 1067 | expr, 1068 | dependency, 1069 | is_dropped: false, 1070 | group: None, 1071 | group_next: None, 1072 | offset: None, 1073 | offset_next: None, 1074 | group_items: Vec::new(), 1075 | group_items_next: Vec::new(), 1076 | group_dependency: Vec::new(), 1077 | group_offset: None, 1078 | } 1079 | } 1080 | fn try_create_independent_strategy(&mut self, ts: &mut TokenStream) -> bool { 1081 | if self.group.is_none() && self.dependency.is_empty() { 1082 | let ident = self.strategy_ident(); 1083 | let expr = &self.expr; 1084 | ts.extend(quote!(let #ident = #expr;)); 1085 | self.group = Some(self.idx); 1086 | self.group_next = self.group; 1087 | self.offset = Some(0); 1088 | self.offset_next = None; 1089 | self.group_items.push(self.idx); 1090 | true 1091 | } else { 1092 | false 1093 | } 1094 | } 1095 | fn strategy_ident(&self) -> Ident { 1096 | parse_str(&format!("strategy_{}", self.idx)).unwrap() 1097 | } 1098 | fn let_sharp_val(&self, from_ref: bool) -> TokenStream { 1099 | let ident = self.key.to_dummy_ident(); 1100 | let expr = if from_ref { 1101 | quote!(#ident) 1102 | } else { 1103 | quote!(&#ident) 1104 | }; 1105 | let expr = if self.by_ref { 1106 | expr 1107 | } else { 1108 | quote!(std::clone::Clone::clone(#expr)) 1109 | }; 1110 | quote!(let #ident = #expr;) 1111 | } 1112 | } 1113 | 1114 | enum StrategyValueType { 1115 | Type(Type), 1116 | Map(TokenStream), 1117 | } 1118 | impl StrategyValueType { 1119 | fn get(&self) -> Type { 1120 | match self { 1121 | StrategyValueType::Type(ty) => ty.clone(), 1122 | StrategyValueType::Map(_) => parse_quote!(_), 1123 | } 1124 | } 1125 | fn any(&self) -> TokenStream { 1126 | match self { 1127 | StrategyValueType::Type(ty) => quote!(proptest::arbitrary::any::<#ty>()), 1128 | StrategyValueType::Map(expr) => quote!(_map_to_any(#expr)), 1129 | } 1130 | } 1131 | fn any_with_args_let(&self, var: TokenStream, init: TokenStream) -> TokenStream { 1132 | match self { 1133 | StrategyValueType::Type(ty) => { 1134 | quote!(let mut #var :<#ty as proptest::arbitrary::Arbitrary>::Parameters = #init;) 1135 | } 1136 | StrategyValueType::Map(expr) => { 1137 | quote!(let mut #var = _map_to_any_with_init(#expr, #init);) 1138 | } 1139 | } 1140 | } 1141 | fn any_with(&self, args: TokenStream) -> TokenStream { 1142 | match self { 1143 | StrategyValueType::Type(ty) => quote!(proptest::arbitrary::any_with::<#ty>(#args)), 1144 | StrategyValueType::Map(expr) => quote!(_map_to_any_with(#expr, #args)), 1145 | } 1146 | } 1147 | } 1148 | struct StrategyExpr { 1149 | expr: TokenStream, 1150 | filters: Vec, 1151 | ty: TokenStream, 1152 | is_jast: bool, 1153 | } 1154 | impl StrategyExpr { 1155 | fn new(ty: TokenStream, expr: TokenStream, is_jast: bool) -> Self { 1156 | Self { 1157 | ty, 1158 | expr, 1159 | is_jast, 1160 | filters: Vec::new(), 1161 | } 1162 | } 1163 | } 1164 | impl ToTokens for StrategyExpr { 1165 | fn to_tokens(&self, tokens: &mut TokenStream) { 1166 | let expr = &self.expr; 1167 | let expr = if self.is_jast { 1168 | let ty = &self.ty; 1169 | quote!(proptest::strategy::Just::<#ty>(#expr)) 1170 | } else { 1171 | quote!(#expr) 1172 | }; 1173 | let expr = if self.filters.is_empty() { 1174 | expr 1175 | } else { 1176 | let mut filter_lets = Vec::new(); 1177 | let var: Ident = parse_quote! { _s }; 1178 | for filter in &self.filters { 1179 | filter_lets.push(filter.make_let_fn(&var)); 1180 | } 1181 | quote! { 1182 | { 1183 | let #var = #expr; 1184 | #(#filter_lets)* 1185 | #var 1186 | } 1187 | } 1188 | }; 1189 | tokens.extend(expr); 1190 | } 1191 | } 1192 | 1193 | fn build_constructor(path: &Path, fields: &Fields, args: TokenStream) -> TokenStream { 1194 | let args = match fields { 1195 | Fields::Named(_) => quote! { {#args} }, 1196 | Fields::Unnamed(_) => quote! { (#args) }, 1197 | Fields::Unit => quote! {}, 1198 | }; 1199 | quote!(#path #args) 1200 | } 1201 | 1202 | fn to_idxs( 1203 | vals: &BTreeMap, 1204 | key_to_idx: &HashMap, 1205 | ) -> Result> { 1206 | let mut idxs = Vec::new(); 1207 | for (key, &span) in vals { 1208 | if let Some(&idx) = key_to_idx.get(key) { 1209 | idxs.push(idx); 1210 | } else { 1211 | bail!(span, "cannot find value `#{}` in this scope.", key); 1212 | } 1213 | } 1214 | idxs.sort_unstable(); 1215 | Ok(idxs) 1216 | } 1217 | 1218 | fn input_type(expr: &Expr) -> Option<&Type> { 1219 | if let Expr::Closure(closure) = expr { 1220 | let inputs = &closure.inputs; 1221 | if inputs.len() == 1 { 1222 | if let Pat::Type(t) = &inputs[0] { 1223 | return Some(&t.ty); 1224 | } 1225 | } 1226 | } 1227 | None 1228 | } 1229 | 1230 | fn cons_tuple(es: &[impl ToTokens]) -> TokenStream { 1231 | match es { 1232 | [] => quote!(()), 1233 | [e0] => quote!(#e0), 1234 | [e0, e1] => quote!((#e0, #e1)), 1235 | [e0, el @ ..] => { 1236 | let el = cons_tuple(el); 1237 | quote!((#e0, #el)) 1238 | } 1239 | } 1240 | } 1241 | -------------------------------------------------------------------------------- /src/bound.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use std::ops::{Deref, DerefMut}; 3 | use syn::Token; 4 | use syn::{ 5 | parse::{discouraged::Speculative, Parse, ParseStream}, 6 | parse_quote, Result, Type, WherePredicate, 7 | }; 8 | 9 | #[allow(clippy::large_enum_variant)] 10 | pub enum Bound { 11 | Type(Type), 12 | Predicate(WherePredicate), 13 | Default { _dotdot: Token![..] }, 14 | } 15 | impl Parse for Bound { 16 | fn parse(input: ParseStream) -> Result { 17 | if input.peek(Token![..]) { 18 | return Ok(Self::Default { 19 | _dotdot: input.parse()?, 20 | }); 21 | } 22 | let fork = input.fork(); 23 | match fork.parse() { 24 | Ok(p) => { 25 | input.advance_to(&fork); 26 | Ok(Self::Predicate(p)) 27 | } 28 | Err(e) => { 29 | if let Ok(ty) = input.parse() { 30 | Ok(Self::Type(ty)) 31 | } else { 32 | Err(e) 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | pub struct Bounds { 40 | pub ty: Vec, 41 | pub pred: Vec, 42 | pub can_extend: bool, 43 | } 44 | impl Bounds { 45 | pub fn new(can_extend: bool) -> Self { 46 | Bounds { 47 | ty: Vec::new(), 48 | pred: Vec::new(), 49 | can_extend, 50 | } 51 | } 52 | pub fn from_data(bound: Option>) -> Self { 53 | if let Some(bound) = bound { 54 | let mut bs = Self::new(false); 55 | for b in bound { 56 | bs.push(b); 57 | } 58 | bs 59 | } else { 60 | Self::new(true) 61 | } 62 | } 63 | fn push(&mut self, bound: Bound) { 64 | match bound { 65 | Bound::Type(ty) => self.ty.push(ty), 66 | Bound::Predicate(pred) => self.pred.push(pred), 67 | Bound::Default { .. } => self.can_extend = true, 68 | } 69 | } 70 | pub fn child(&mut self, bound: Option>) -> BoundsChild { 71 | let bounds = if self.can_extend { 72 | Self::from_data(bound) 73 | } else { 74 | Self::new(false) 75 | }; 76 | BoundsChild { 77 | owner: self, 78 | bounds, 79 | } 80 | } 81 | pub fn build_wheres(self, type_param_bounds: TokenStream) -> Vec { 82 | let mut pred = self.pred; 83 | for ty in self.ty { 84 | pred.push(parse_quote!(#ty : #type_param_bounds)); 85 | } 86 | pred 87 | } 88 | } 89 | pub struct BoundsChild<'a> { 90 | owner: &'a mut Bounds, 91 | bounds: Bounds, 92 | } 93 | impl Deref for BoundsChild<'_> { 94 | type Target = Bounds; 95 | 96 | fn deref(&self) -> &Self::Target { 97 | &self.bounds 98 | } 99 | } 100 | impl DerefMut for BoundsChild<'_> { 101 | fn deref_mut(&mut self) -> &mut Self::Target { 102 | &mut self.bounds 103 | } 104 | } 105 | impl Drop for BoundsChild<'_> { 106 | fn drop(&mut self) { 107 | if self.owner.can_extend { 108 | self.owner.ty.append(&mut self.bounds.ty); 109 | self.owner.pred.append(&mut self.bounds.pred); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // #![include_doc("../README.md", start("This crate provides two procedural macros, `#[derive(Arbitrary)]` and `#[proptest]`."))] 2 | //! This crate provides two procedural macros, `#[derive(Arbitrary)]` and `#[proptest]`. 3 | //! 4 | //! Each of these macros is an alternative to the following proptest's official macros. 5 | //! 6 | //! | [test-strategy][] | [proptest][] | [proptest-derive][] | 7 | //! | ------------------------------------------ | ------------------------------ | ------------------------------------ | 8 | //! | [`#[derive(Arbitrary)]`](#derivearbitrary) | | [`#[derive(Arbitrary)]`][official-a] | 9 | //! | [`#[proptest]`](#proptest) | [`proptest ! { }`][official-m] | | 10 | //! 11 | //! [test-strategy]: https://crates.io/crates/test-strategy 12 | //! [proptest]: https://crates.io/crates/proptest 13 | //! [proptest-derive]: https://crates.io/crates/proptest-derive 14 | //! [official-m]: https://altsysrq.github.io/rustdoc/proptest/latest/proptest/macro.proptest.html 15 | //! [official-a]: https://altsysrq.github.io/proptest-book/proptest-derive/modifiers.html 16 | //! 17 | //! The macros provided by this crate have the following advantages over the proptest's official macros. 18 | //! 19 | //! - Supports higher-order strategies. (`#[derive(Arbitrary)]` and `#[proptest]`) 20 | //! - Code formatting is not disabled. (`#[proptest]`) 21 | //! 22 | //! However, the syntax of this crate's macros are not compatible with the syntax of the official macros. 23 | //! 24 | //! ## Install 25 | //! 26 | //! Add this to your Cargo.toml: 27 | //! 28 | //! ```toml 29 | //! [dependencies] 30 | //! test-strategy = "0.4.1" 31 | //! proptest = "1.6.0" 32 | //! ``` 33 | //! 34 | //! ## Example 35 | //! 36 | //! You can use `#[derive(Arbitrary)]` to automatically implement proptest's `Arbitrary` trait. 37 | //! 38 | //! ```rust 39 | //! use test_strategy::Arbitrary; 40 | //! 41 | //! #[derive(Arbitrary, Debug)] 42 | //! struct TestInputStruct { 43 | //! x: u32, 44 | //! 45 | //! #[strategy(1..10u32)] 46 | //! y: u32, 47 | //! 48 | //! #[strategy(0..#y)] 49 | //! z: u32, 50 | //! } 51 | //! 52 | //! #[derive(Arbitrary, Debug)] 53 | //! enum TestInputEnum { 54 | //! A, 55 | //! B, 56 | //! #[weight(3)] 57 | //! C, 58 | //! X(u32), 59 | //! Y(#[strategy(0..10u32)] u32), 60 | //! } 61 | //! ``` 62 | //! 63 | //! You can define a property test by adding `#[proptest]` to the function. 64 | //! 65 | //! ```rust 66 | //! use test_strategy::proptest; 67 | //! 68 | //! #[proptest] 69 | //! fn my_test(_x: u32, #[strategy(1..10u32)] y: u32, #[strategy(0..#y)] z: u32) { 70 | //! assert!(1 <= y && y < 10); 71 | //! assert!(z <= y); 72 | //! } 73 | //! ``` 74 | //! 75 | //! ## Attributes 76 | //! 77 | //! Attributes can be written in the following positions. 78 | //! 79 | //! | attribute | function | struct | enum | variant | field | function parameter | 80 | //! | --------------------------------------------------- | -------- | ------ | ---- | ------- | ----- | ------------------ | 81 | //! | [`#[strategy]`](#strategy) | | | | | ✔ | ✔ | 82 | //! | [`#[any]`](#any) | | | | | ✔ | ✔ | 83 | //! | [`#[weight]`](#weight) | | | | ✔ | | | 84 | //! | [`#[map]`](#map) | | | | | ✔ | ✔ | 85 | //! | [`#[filter]`](#filter) | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | 86 | //! | [`#[by_ref]`](#by_ref) | | | | | ✔ | ✔ | 87 | //! | [`#[arbitrary(args = T)]`](#arbitraryargs--t) | | ✔ | ✔ | | | | 88 | //! | [`#[arbitrary(bound(...))]`](#arbitraryboundt1-t2-) | | ✔ | ✔ | ✔ | ✔ | | 89 | //! | [`#[arbitrary(dump)]`](#arbitrarydump) | | ✔ | ✔ | | | | 90 | //! | [`#[proptest]`](#proptest) | ✔ | | | | | | 91 | //! | [`#[proptest(async = ...)]`](#proptestasync--) | ✔ | | | | | | 92 | //! | [`#[proptest(dump)]`](#proptestdump) | ✔ | | | | | | 93 | //! 94 | //! ## `#[derive(Arbitrary)]` 95 | //! 96 | //! You can implement `proptest::arbitrary::Arbitrary` automatically by adding `#[derive(Arbitrary)]` to struct or enum declaration. 97 | //! 98 | //! By default, all fields are set using the strategy obtained by `proptest::arbitrary::any()`. 99 | //! 100 | //! So the following two codes are equivalent. 101 | //! 102 | //! ```rust 103 | //! use test_strategy::Arbitrary; 104 | //! 105 | //! #[derive(Arbitrary, Debug)] 106 | //! struct TestInput { 107 | //! x: u32, 108 | //! y: u32, 109 | //! } 110 | //! ``` 111 | //! 112 | //! ```rust 113 | //! use proptest::{ 114 | //! arbitrary::{any, Arbitrary}, 115 | //! strategy::{BoxedStrategy, Strategy}, 116 | //! }; 117 | //! 118 | //! #[derive(Debug)] 119 | //! struct TestInput { 120 | //! x: u32, 121 | //! y: u32, 122 | //! } 123 | //! impl Arbitrary for TestInput { 124 | //! type Parameters = (); 125 | //! type Strategy = BoxedStrategy; 126 | //! 127 | //! fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { 128 | //! let x = any::(); 129 | //! let y = any::(); 130 | //! (x, y).prop_map(|(x, y)| Self { x, y }).boxed() 131 | //! } 132 | //! } 133 | //! ``` 134 | //! 135 | //! ## `#[strategy]` 136 | //! 137 | //! You can specify a strategy to generate values for the field by adding `#[strategy(...)]` to the field. 138 | //! 139 | //! In the following example, the value of field `x` will be less than 20. 140 | //! 141 | //! ```rust 142 | //! use test_strategy::Arbitrary; 143 | //! 144 | //! #[derive(Arbitrary, Debug)] 145 | //! struct TestInput { 146 | //! #[strategy(0..20u32)] 147 | //! x: u32, 148 | //! } 149 | //! ``` 150 | //! 151 | //! In `#[strategy]`, the values of other fields can be used by following `#` to the name of the field. 152 | //! 153 | //! In the following example, the value of `y` is less than or equal to `x`. 154 | //! 155 | //! ```rust 156 | //! use test_strategy::Arbitrary; 157 | //! 158 | //! #[derive(Arbitrary, Debug)] 159 | //! struct TestInput { 160 | //! x: u32, 161 | //! #[strategy(0..=#x)] 162 | //! y: u32, 163 | //! } 164 | //! ``` 165 | //! 166 | //! ## `#[any]` 167 | //! 168 | //! Instead of writing `#[strategy(any_with::(expr))]`, you can write `#[any(expr)]`. 169 | //! 170 | //! ```rust 171 | //! use proptest::collection::size_range; 172 | //! use test_strategy::Arbitrary; 173 | //! 174 | //! #[derive(Arbitrary, Debug, PartialEq)] 175 | //! struct TestInput { 176 | //! #[any(size_range(0..16).lift())] 177 | //! x: Vec, 178 | //! } 179 | //! ``` 180 | //! 181 | //! Instead of writing an expression to be passed to `any_with`, you can write only the value of the field to be changed from the default value. 182 | //! 183 | //! Therefore, the following `TestInputA`, `TestInputB` and `TestInputC` are equivalent. 184 | //! 185 | //! ```rust 186 | //! use test_strategy::Arbitrary; 187 | //! 188 | //! #[derive(Arbitrary, Debug)] 189 | //! struct TestInputA { 190 | //! #[any(InnerArgs { upper : 20, ..InnerArgs::default() })] 191 | //! a: Inner, 192 | //! } 193 | //! #[derive(Arbitrary, Debug)] 194 | //! struct TestInputB { 195 | //! #[any(InnerArgs::default(), upper = 20)] 196 | //! a: Inner, 197 | //! } 198 | //! #[derive(Arbitrary, Debug)] 199 | //! struct TestInputC { 200 | //! #[any(upper = 20)] 201 | //! a: Inner, 202 | //! } 203 | //! 204 | //! #[derive(Default)] 205 | //! struct InnerArgs { 206 | //! lower: i32, 207 | //! upper: i32, 208 | //! } 209 | //! 210 | //! #[derive(Arbitrary, Debug)] 211 | //! #[arbitrary(args = InnerArgs)] 212 | //! struct Inner { 213 | //! #[strategy(args.lower..args.upper)] 214 | //! x: i32, 215 | //! } 216 | //! ``` 217 | //! 218 | //! ## `#[weight]` 219 | //! 220 | //! By default, all variants appear with equal probability. 221 | //! 222 | //! You can add `#[weight]` to the variant to change the probability of the variant appearing. 223 | //! 224 | //! In the following example, `TestInput::B` is twice as likely to appear as `TestInput::A`. 225 | //! 226 | //! ```rust 227 | //! use test_strategy::Arbitrary; 228 | //! 229 | //! #[derive(Arbitrary, Debug)] 230 | //! enum TestInput { 231 | //! A, 232 | //! 233 | //! #[weight(2)] 234 | //! B, 235 | //! } 236 | //! ``` 237 | //! 238 | //! If you add `#[weight(0)]` to a variant, the variant does not appear, so you can use a type in that variant that cannot be used as `Arbitrary`. 239 | //! 240 | //! ```rust 241 | //! use test_strategy::Arbitrary; 242 | //! 243 | //! #[derive(Debug)] 244 | //! struct NotArbitrary; 245 | //! 246 | //! #[derive(Arbitrary, Debug)] 247 | //! enum TestInput { 248 | //! A, 249 | //! 250 | //! #[allow(dead_code)] 251 | //! #[weight(0)] // Removing this `#[weight(0)]` will cause a compile error. 252 | //! B(NotArbitrary), 253 | //! } 254 | //! ``` 255 | //! 256 | //! ## `#[map]` 257 | //! 258 | //! Instead of using `prop_map` in `#[strategy(...)]`, `#[map(...)]` can be used. 259 | //! 260 | //! The following codes mean the same thing. 261 | //! 262 | //! ```rust 263 | //! use proptest::arbitrary::any; 264 | //! use proptest::strategy::Strategy; 265 | //! use test_strategy::Arbitrary; 266 | //! 267 | //! #[derive(Arbitrary, Debug)] 268 | //! struct TestInput1 { 269 | //! #[strategy(any::().prop_map(|x| x + 1))] 270 | //! x: u32, 271 | //! } 272 | //! 273 | //! #[derive(Arbitrary, Debug)] 274 | //! struct TestInput2 { 275 | //! #[strategy(any::())] 276 | //! #[map(|x| x + 1)] 277 | //! x: u32, 278 | //! } 279 | //! 280 | //! #[derive(Arbitrary, Debug)] 281 | //! struct TestInput3 { 282 | //! #[map(|x: u32| x + 1)] 283 | //! x: u32, 284 | //! } 285 | //! ``` 286 | //! 287 | //! References to other fields in the function applied to `prop_map` or `#[map(...)]` will generate different strategies. 288 | //! 289 | //! Referencing another field in `#[strategy(...)]` will expand it to `prop_flat_map`, even if it is in `prop_map`. 290 | //! 291 | //! ```rust 292 | //! use proptest::arbitrary::any; 293 | //! use proptest::strategy::{Just, Strategy}; 294 | //! use test_strategy::Arbitrary; 295 | //! 296 | //! #[derive(Arbitrary, Debug)] 297 | //! struct T1 { 298 | //! x: u32, 299 | //! 300 | //! #[strategy(any::().prop_map(move |y| #x + y))] 301 | //! y: u32, 302 | //! } 303 | //! // The code above generates the following strategy. 304 | //! let t1 = any::() 305 | //! .prop_flat_map(|x| (Just(x), any::().prop_map(move |y| x + y))) 306 | //! .prop_map(|(x, y)| T1 { x, y }); 307 | //! ``` 308 | //! 309 | //! On the other hand, if you refer to another field in `#[map]`, it will expand to `prop_map`. 310 | //! 311 | //! ```rust 312 | //! use proptest::arbitrary::any; 313 | //! use proptest::strategy::Strategy; 314 | //! use test_strategy::Arbitrary; 315 | //! 316 | //! #[derive(Arbitrary, Debug)] 317 | //! struct T2 { 318 | //! x: u32, 319 | //! 320 | //! #[map(|y: u32| #x + y)] 321 | //! y: u32, 322 | //! } 323 | //! // The code above generates the following strategy. 324 | //! let t2 = (any::(), any::()).prop_map(|(x, y)| T2 { x, y }); 325 | //! ``` 326 | //! 327 | //! If the input and output types of the function specified in `#[map]` are different, the value type of the strategy set in `#[strategy]` is the type of the function's input, not the type of the field. 328 | //! 329 | //! ```rust 330 | //! use proptest::arbitrary::any; 331 | //! use proptest::sample::Index; 332 | //! use test_strategy::Arbitrary; 333 | //! 334 | //! #[derive(Arbitrary, Debug)] 335 | //! struct T1 { 336 | //! #[strategy(any::())] 337 | //! #[map(|i: Index| i.index(10))] 338 | //! x: usize, 339 | //! } 340 | //! 341 | //! // `#[strategy(any::())]` can be omitted. 342 | //! #[derive(Arbitrary, Debug)] 343 | //! struct T2 { 344 | //! #[map(|i: Index| i.index(10))] 345 | //! x: usize, 346 | //! } 347 | //! ``` 348 | //! 349 | //! ## `#[filter]` 350 | //! 351 | //! By adding `#[filter]` , you can limit the values generated. 352 | //! 353 | //! In the following examples, x is an even number. 354 | //! 355 | //! ```rust 356 | //! use test_strategy::Arbitrary; 357 | //! 358 | //! #[derive(Arbitrary, Debug)] 359 | //! struct TestInput { 360 | //! #[filter(#x % 2 == 0)] 361 | //! x: u32, 362 | //! } 363 | //! ``` 364 | //! 365 | //! You can also use multiple variables in a predicate. 366 | //! 367 | //! ```rust 368 | //! use test_strategy::Arbitrary; 369 | //! 370 | //! #[derive(Arbitrary, Debug)] 371 | //! #[filter((#x + #y) % 2 == 0)] 372 | //! struct T1 { 373 | //! x: u32, 374 | //! y: u32, 375 | //! } 376 | //! 377 | //! #[derive(Arbitrary, Debug)] 378 | //! struct T2 { 379 | //! x: u32, 380 | //! #[filter((#x + #y) % 2 == 0)] 381 | //! y: u32, 382 | //! } 383 | //! ``` 384 | //! 385 | //! You can use the value of a structure or enum in the filter by using `#self`. 386 | //! 387 | //! ```rust 388 | //! use test_strategy::Arbitrary; 389 | //! 390 | //! #[derive(Arbitrary, Debug)] 391 | //! #[filter((#self.x + #self.y) % 2 == 0)] 392 | //! struct TestInput { 393 | //! x: u32, 394 | //! y: u32, 395 | //! } 396 | //! ``` 397 | //! 398 | //! If the expression specified for `#[filter]` does not contain a variable named by appending # to its own field name, the expression is treated as a predicate function, rather than an expression that returns a bool. 399 | //! 400 | //! ```rust 401 | //! use test_strategy::Arbitrary; 402 | //! 403 | //! #[derive(Arbitrary, Debug)] 404 | //! struct TestInput { 405 | //! #[filter(is_even)] 406 | //! x: u32, 407 | //! } 408 | //! fn is_even(x: &u32) -> bool { 409 | //! x % 2 == 0 410 | //! } 411 | //! 412 | //! #[derive(Arbitrary, Debug)] 413 | //! struct T2 { 414 | //! a: u32, 415 | //! 416 | //! // Since `#a` exists but `#b` does not, it is treated as a predicate function. 417 | //! #[filter(|&x| x > #a)] 418 | //! b: u32, 419 | //! } 420 | //! ``` 421 | //! 422 | //! Similarly, an expression that does not contain `#self` in the `#[filter(...)]` that it attaches to a type is treated as a predicate function. 423 | //! 424 | //! ```rust 425 | //! use test_strategy::Arbitrary; 426 | //! 427 | //! #[derive(Arbitrary, Debug)] 428 | //! #[filter(is_even)] 429 | //! struct T { 430 | //! x: u32, 431 | //! } 432 | //! fn is_even(t: &T) -> bool { 433 | //! t.x % 2 == 0 434 | //! } 435 | //! ``` 436 | //! 437 | //! You can specify a filter name by passing two arguments to `#[filter]`. 438 | //! 439 | //! ```rust 440 | //! use test_strategy::Arbitrary; 441 | //! 442 | //! #[derive(Arbitrary, Debug)] 443 | //! struct TestInput { 444 | //! #[filter("x is even", #x % 2 == 0)] 445 | //! x: u32, 446 | //! } 447 | //! ``` 448 | //! 449 | //! ## `#[by_ref]` 450 | //! 451 | //! By default, if you use a variable with `#[strategy]`, `#[any]`, `#[map]` or `#[filter]` with `#` attached to it, the cloned value is set. 452 | //! 453 | //! Adding `#[by_ref]` to the field makes it use the reference instead of the cloned value. 454 | //! 455 | //! ```rust 456 | //! use test_strategy::Arbitrary; 457 | //! 458 | //! #[derive(Arbitrary, Debug)] 459 | //! struct TestInput { 460 | //! #[by_ref] 461 | //! #[strategy(1..10u32)] 462 | //! x: u32, 463 | //! 464 | //! #[strategy(0..*#x)] 465 | //! y: u32, 466 | //! } 467 | //! ``` 468 | //! 469 | //! ## `#[arbitrary]` 470 | //! 471 | //! ### `#[arbitrary(args = T)]` 472 | //! 473 | //! Specifies the type of `Arbitrary::Parameters`. 474 | //! 475 | //! You can use the `Rc` value of this type in `#[strategy]`, `#[any]`, or `#[filter]` with the variable name `args`. 476 | //! 477 | //! ```rust 478 | //! use test_strategy::Arbitrary; 479 | //! 480 | //! #[derive(Debug, Default)] 481 | //! struct TestInputArgs { 482 | //! x_max: u32, 483 | //! } 484 | //! 485 | //! #[derive(Arbitrary, Debug)] 486 | //! #[arbitrary(args = TestInputArgs)] 487 | //! struct TestInput { 488 | //! #[strategy(0..=args.x_max)] 489 | //! x: u32, 490 | //! } 491 | //! ``` 492 | //! 493 | //! ### `#[arbitrary(bound(T1, T2, ..))]` 494 | //! 495 | //! By default, if the type of field for which `#[strategy]` is not specified contains a generic parameter, that type is set to trait bounds. 496 | //! 497 | //! Therefore, the following `TestInputA` and `TestInputB` are equivalent. 498 | //! 499 | //! ```rust 500 | //! use proptest::{ 501 | //! arbitrary::any, arbitrary::Arbitrary, strategy::BoxedStrategy, strategy::Strategy, 502 | //! }; 503 | //! use test_strategy::Arbitrary; 504 | //! 505 | //! #[derive(Arbitrary, Debug)] 506 | //! struct TestInputA { 507 | //! x: T, 508 | //! } 509 | //! 510 | //! #[derive(Debug)] 511 | //! struct TestInputB { 512 | //! x: T, 513 | //! } 514 | //! impl Arbitrary for TestInputB { 515 | //! type Parameters = (); 516 | //! type Strategy = BoxedStrategy; 517 | //! 518 | //! fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { 519 | //! any::().prop_map(|x| Self { x }).boxed() 520 | //! } 521 | //! } 522 | //! ``` 523 | //! 524 | //! Types of fields with `#[strategy]` do not set trait bounds automatically, so you need to set trait bound manually with `#[arbitrary(bound(T))]`. 525 | //! 526 | //! ```rust 527 | //! use proptest::arbitrary::any_with; 528 | //! use test_strategy::Arbitrary; 529 | //! 530 | //! #[derive(Arbitrary, Debug, PartialEq)] 531 | //! #[arbitrary(bound(T))] 532 | //! struct TestInput { 533 | //! #[strategy(any_with::(Default::default()))] 534 | //! x: T, 535 | //! } 536 | //! ``` 537 | //! 538 | //! You can also specify where predicate instead of type. 539 | //! 540 | //! ```rust 541 | //! use proptest::arbitrary::{any_with, Arbitrary}; 542 | //! use test_strategy::Arbitrary; 543 | //! 544 | //! #[derive(Arbitrary, Debug, PartialEq)] 545 | //! #[arbitrary(bound(T : Arbitrary + 'static))] 546 | //! struct TestInput { 547 | //! #[strategy(any_with::(Default::default()))] 548 | //! x: T, 549 | //! } 550 | //! ``` 551 | //! 552 | //! `..` means automatically generated trait bounds. 553 | //! 554 | //! The following example uses a manually specified trait bounds in addition to the automatically generated trait bounds. 555 | //! 556 | //! ```rust 557 | //! use proptest::arbitrary::any_with; 558 | //! use test_strategy::Arbitrary; 559 | //! 560 | //! #[derive(Arbitrary, Debug, PartialEq)] 561 | //! #[arbitrary(bound(T1, ..))] 562 | //! struct TestInput { 563 | //! #[strategy(any_with::(Default::default()))] 564 | //! x: T1, 565 | //! 566 | //! y: T2, 567 | //! } 568 | //! ``` 569 | //! 570 | //! ### `#[arbitrary(dump)]` 571 | //! 572 | //! Causes a compile error and outputs the code generated by `#[derive(Arbitrary)]` as an error message. 573 | //! 574 | //! ## `#[proptest]` 575 | //! 576 | //! `#[proptest]` is the attribute used instead of `#[test]` when defining a property test. 577 | //! 578 | //! The following example defines a test that takes a variety of integers as input. 579 | //! 580 | //! ```rust 581 | //! use test_strategy::proptest; 582 | //! 583 | //! #[proptest] 584 | //! fn my_test(_input: i32) { 585 | //! // ... 586 | //! } 587 | //! ``` 588 | //! 589 | //! You can add `#[strategy]`, `#[any]`, `#[filter]`, `#[by_ref]` to the parameter of the function with `# [proptest]`. 590 | //! 591 | //! ```rust 592 | //! use test_strategy::proptest; 593 | //! 594 | //! #[proptest] 595 | //! fn my_test2(#[strategy(10..20)] _input: i32) { 596 | //! // ... 597 | //! } 598 | //! ``` 599 | //! 600 | //! You can change the configuration of a property test by setting the argument of `#[proptest]` attribute to a value of [`proptest::prelude::ProptestConfig`](https://docs.rs/proptest/latest/proptest/prelude/index.html#reexport.ProptestConfig) type. 601 | //! 602 | //! ```rust 603 | //! use proptest::prelude::ProptestConfig; 604 | //! use test_strategy::proptest; 605 | //! 606 | //! #[proptest(ProptestConfig { cases : 1000, ..ProptestConfig::default() })] 607 | //! fn my_test_with_config(_input: i32) { 608 | //! // ... 609 | //! } 610 | //! ``` 611 | //! 612 | //! As with `#[any]`, you can also set only the value of the field to be changed from the default value. 613 | //! 614 | //! The example below is equivalent to the one above. 615 | //! 616 | //! ```rust 617 | //! use proptest::prelude::ProptestConfig; 618 | //! use test_strategy::proptest; 619 | //! 620 | //! #[proptest(ProptestConfig::default(), cases = 1000)] 621 | //! fn my_test_with_config_2(_input: i32) { 622 | //! // ... 623 | //! } 624 | //! 625 | //! #[proptest(cases = 1000)] 626 | //! fn my_test_with_config_3(_input: i32) { 627 | //! // ... 628 | //! } 629 | //! ``` 630 | //! 631 | //! ### `#[proptest(async = ...)]` 632 | //! 633 | //! Async functions can be tested by setting `async = ...` to the argument of `#[proptest]`. 634 | //! 635 | //! The following values are allowed after `async =`. 636 | //! The value specifies the asynchronous runtime used for the test. 637 | //! 638 | //! - "tokio" 639 | //! 640 | //! ```toml 641 | //! [dev-dependencies] 642 | //! test-strategy = "0.4.1" 643 | //! proptest = "1.6.0" 644 | //! tokio = { version = "1.38.0", features = ["rt-multi-thread"] } 645 | //! ``` 646 | //! 647 | //! ```rust 648 | //! use test_strategy::proptest; 649 | //! use proptest::prop_assert; 650 | //! 651 | //! #[proptest(async = "tokio")] 652 | //! async fn my_test_async() { 653 | //! async { }.await; 654 | //! prop_assert!(true); 655 | //! } 656 | //! ``` 657 | //! 658 | //! ### `#[proptest(dump)]` 659 | //! 660 | //! You can use `#[proptest(dump)]` and output the code generated by `#[proptest]` as an compile error message. 661 | //! 662 | //! ```compile_fail 663 | //! #[proptest(dump)] 664 | //! fn my_test(_input: i32) { 665 | //! // ... 666 | //! } 667 | //! ``` 668 | // #![include_doc("../README.md", end("## License"))] 669 | 670 | extern crate proc_macro; 671 | 672 | #[macro_use] 673 | mod syn_utils; 674 | mod arbitrary; 675 | mod bound; 676 | mod proptest_fn; 677 | 678 | use syn::{parse_macro_input, DeriveInput, ItemFn}; 679 | use syn_utils::into_macro_output; 680 | 681 | #[proc_macro_attribute] 682 | pub fn proptest( 683 | attr: proc_macro::TokenStream, 684 | item: proc_macro::TokenStream, 685 | ) -> proc_macro::TokenStream { 686 | let item_fn = parse_macro_input!(item as ItemFn); 687 | into_macro_output(proptest_fn::build_proptest(attr.into(), item_fn)) 688 | } 689 | 690 | #[proc_macro_derive( 691 | Arbitrary, 692 | attributes(arbitrary, strategy, any, map, filter, weight, by_ref) 693 | )] 694 | pub fn derive_arbitrary(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 695 | let input = parse_macro_input!(input as DeriveInput); 696 | into_macro_output(arbitrary::derive_arbitrary(input)) 697 | } 698 | -------------------------------------------------------------------------------- /src/proptest_fn.rs: -------------------------------------------------------------------------------- 1 | use std::mem::replace; 2 | 3 | use crate::syn_utils::{Arg, Args}; 4 | use proc_macro2::{Span, TokenStream}; 5 | use quote::{quote, ToTokens}; 6 | use syn::{ 7 | parse2, parse_quote, parse_str, spanned::Spanned, token, Attribute, Block, Expr, Field, 8 | FieldMutability, FnArg, Ident, ItemFn, LitStr, Pat, Result, ReturnType, Visibility, 9 | }; 10 | 11 | // Check whether given attribute is a test attribute of forms: 12 | // * `#[test]` 13 | // * `#[core::prelude::*::test]` or `#[::core::prelude::*::test]` 14 | // * `#[std::prelude::*::test]` or `#[::std::prelude::*::test]` 15 | fn is_test_attribute(attr: &Attribute) -> bool { 16 | let path = match &attr.meta { 17 | syn::Meta::Path(path) => path, 18 | _ => return false, 19 | }; 20 | const CANDIDATES_LEN: usize = 4; 21 | 22 | let candidates: [[&str; CANDIDATES_LEN]; 2] = [ 23 | ["core", "prelude", "*", "test"], 24 | ["std", "prelude", "*", "test"], 25 | ]; 26 | if path.leading_colon.is_none() 27 | && path.segments.len() == 1 28 | && path.segments[0].arguments.is_none() 29 | && path.segments[0].ident == "test" 30 | { 31 | return true; 32 | } else if path.segments.len() != candidates[0].len() { 33 | return false; 34 | } 35 | candidates.into_iter().any(|segments| { 36 | path.segments.iter().zip(segments).all(|(segment, path)| { 37 | segment.arguments.is_none() && (path == "*" || segment.ident == path) 38 | }) 39 | }) 40 | } 41 | 42 | pub fn build_proptest(attr: TokenStream, mut item_fn: ItemFn) -> Result { 43 | let mut attr_args = None; 44 | if !attr.is_empty() { 45 | attr_args = Some(parse2::(attr)?); 46 | } 47 | let mut dump = false; 48 | item_fn.attrs.retain(|attr| { 49 | if attr.path().is_ident("proptest_dump") { 50 | dump = true; 51 | false 52 | } else { 53 | true 54 | } 55 | }); 56 | let (mut attr_args, config_args) = TestFnAttrArgs::from(attr_args.unwrap_or_default())?; 57 | dump |= attr_args.dump; 58 | let args_type_str = format!("_{}Args", to_camel_case(&item_fn.sig.ident.to_string())); 59 | let args_type_ident: Ident = parse_str(&args_type_str).unwrap(); 60 | let args = item_fn 61 | .sig 62 | .inputs 63 | .iter() 64 | .map(TestFnArg::from) 65 | .collect::>>()?; 66 | let args_pats = args.iter().map(|arg| arg.pat()); 67 | let block = &item_fn.block; 68 | if item_fn.sig.asyncness.is_none() { 69 | attr_args.r#async = None; 70 | } 71 | let output = replace(&mut item_fn.sig.output, ReturnType::Default); 72 | let block = if let Some(a) = attr_args.r#async { 73 | item_fn.sig.asyncness = None; 74 | a.apply(block, output) 75 | } else { 76 | match output { 77 | ReturnType::Default => quote!(#block), 78 | ReturnType::Type(_, ty) => { 79 | let f = Ident::new("__test_body", Span::mixed_site()); 80 | quote!({ 81 | let #f = move || -> #ty { 82 | #block 83 | }; 84 | ::std::result::Result::map_err(#f(), 85 | |e| ::proptest::test_runner::TestCaseError::fail(::std::string::ToString::to_string(&e)))?; 86 | }) 87 | } 88 | } 89 | }; 90 | let block = quote! { 91 | { 92 | let #args_type_ident { #(#args_pats,)* } = input; 93 | #block 94 | } 95 | }; 96 | item_fn.sig.inputs = parse_quote! { input: #args_type_ident }; 97 | item_fn.block = Box::new(parse2(block)?); 98 | if !item_fn.attrs.iter().any(is_test_attribute) { 99 | let test_attr: Attribute = parse_quote! { 100 | #[::core::prelude::v1::test] 101 | }; 102 | item_fn.attrs.push(test_attr); 103 | } 104 | let args_fields = args.iter().map(|arg| &arg.field); 105 | let config = to_proptest_config(config_args); 106 | let ts = quote! { 107 | #[cfg(test)] 108 | #[derive(test_strategy::Arbitrary, Debug)] 109 | struct #args_type_ident { 110 | #(#args_fields,)* 111 | } 112 | #[cfg(test)] 113 | proptest::proptest! { 114 | #config 115 | #item_fn 116 | } 117 | }; 118 | if dump { 119 | panic!("{}", ts); 120 | } 121 | Ok(ts) 122 | } 123 | 124 | fn to_proptest_config(args: Args) -> TokenStream { 125 | if args.is_empty() { 126 | return quote!(); 127 | } 128 | let mut base_expr = None; 129 | let mut inits = Vec::new(); 130 | for arg in args { 131 | match arg { 132 | Arg::Value(value) => base_expr = Some(value), 133 | Arg::NameValue { name, value, .. } => inits.push(quote!(#name : #value)), 134 | } 135 | } 136 | let base_expr = base_expr.unwrap_or_else(|| { 137 | parse_quote!(::default()) 138 | }); 139 | quote! { 140 | #![proptest_config(proptest::test_runner::Config { 141 | #(#inits,)* 142 | .. #base_expr 143 | })] 144 | } 145 | } 146 | struct TestFnArg { 147 | field: Field, 148 | mutability: Option, 149 | } 150 | impl TestFnArg { 151 | fn from(arg: &FnArg) -> Result { 152 | if let FnArg::Typed(arg) = arg { 153 | if let Pat::Ident(ident) = arg.pat.as_ref() { 154 | if ident.attrs.is_empty() && ident.by_ref.is_none() && ident.subpat.is_none() { 155 | return Ok(Self { 156 | field: Field { 157 | attrs: arg.attrs.clone(), 158 | vis: Visibility::Inherited, 159 | mutability: FieldMutability::None, 160 | ident: Some(ident.ident.clone()), 161 | colon_token: Some(arg.colon_token), 162 | ty: arg.ty.as_ref().clone(), 163 | }, 164 | mutability: ident.mutability, 165 | }); 166 | } 167 | } else { 168 | bail!(arg.pat.span(), "argument pattern not supported."); 169 | } 170 | } 171 | bail!( 172 | arg.span(), 173 | "argument {} is not supported.", 174 | arg.to_token_stream() 175 | ); 176 | } 177 | fn pat(&self) -> TokenStream { 178 | let mutability = &self.mutability; 179 | let ident = &self.field.ident; 180 | quote!(#mutability #ident) 181 | } 182 | } 183 | 184 | #[derive(Debug, Clone)] 185 | enum Async { 186 | Tokio, 187 | Expr(Expr), 188 | } 189 | impl Async { 190 | fn apply(&self, block: &Block, output: ReturnType) -> TokenStream { 191 | let body; 192 | let output_type; 193 | let ret_expr; 194 | match output { 195 | ReturnType::Default => { 196 | body = quote! { 197 | #block 198 | Ok(()) 199 | }; 200 | output_type = 201 | quote!(::core::result::Result<_, ::proptest::test_runner::TestCaseError> ); 202 | ret_expr = quote! { ret? }; 203 | } 204 | ReturnType::Type(_, ty) => { 205 | body = quote! { #block }; 206 | output_type = quote!(#ty); 207 | ret_expr = quote! { 208 | ::std::result::Result::map_err(ret, 209 | |e| ::proptest::test_runner::TestCaseError::fail(::std::string::ToString::to_string(&e)))? 210 | }; 211 | } 212 | } 213 | match self { 214 | Async::Tokio => { 215 | quote! { 216 | let ret: #output_type = 217 | tokio::runtime::Runtime::new() 218 | .unwrap() 219 | .block_on(async move { #body }); 220 | #ret_expr; 221 | } 222 | } 223 | Async::Expr(expr) => { 224 | quote! { 225 | let ret: #output_type = 226 | (#expr)(async move { #body }); 227 | #ret_expr; 228 | } 229 | } 230 | } 231 | } 232 | } 233 | impl syn::parse::Parse for Async { 234 | fn parse(input: syn::parse::ParseStream) -> Result { 235 | if input.peek(LitStr) { 236 | let s: LitStr = input.parse()?; 237 | match s.value().as_str() { 238 | "tokio" => Ok(Async::Tokio), 239 | _ => bail!(s.span(), "expected `tokio`."), 240 | } 241 | } else { 242 | Ok(Async::Expr(input.parse()?)) 243 | } 244 | } 245 | } 246 | 247 | struct TestFnAttrArgs { 248 | r#async: Option, 249 | dump: bool, 250 | } 251 | impl TestFnAttrArgs { 252 | fn from(args: Args) -> Result<(Self, Args)> { 253 | let mut config_args = Args::new(); 254 | let mut this = TestFnAttrArgs { 255 | r#async: None, 256 | dump: false, 257 | }; 258 | for arg in args { 259 | if let Arg::NameValue { name, value, .. } = &arg { 260 | if name == "async" { 261 | this.r#async = Some(parse2(value.to_token_stream())?); 262 | continue; 263 | } 264 | } 265 | if let Arg::Value(value) = &arg { 266 | if value == &parse_quote!(dump) { 267 | this.dump = true; 268 | continue; 269 | } 270 | } 271 | config_args.0.push(arg); 272 | } 273 | Ok((this, config_args)) 274 | } 275 | } 276 | 277 | fn to_camel_case(s: &str) -> String { 278 | let mut upper = true; 279 | let mut r = String::new(); 280 | for c in s.chars() { 281 | if c == '_' { 282 | upper = true; 283 | } else if upper { 284 | r.push_str(&c.to_uppercase().to_string()); 285 | upper = false; 286 | } else { 287 | r.push(c); 288 | } 289 | } 290 | r 291 | } 292 | -------------------------------------------------------------------------------- /src/syn_utils.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Group, Spacing, Span, TokenStream, TokenTree}; 2 | use quote::{quote, ToTokens}; 3 | use std::{ 4 | collections::{BTreeMap, HashSet}, 5 | iter::once, 6 | ops::Deref, 7 | }; 8 | use structmeta::{Parse, ToTokens}; 9 | use syn::{ 10 | ext::IdentExt, 11 | parenthesized, 12 | parse::{Parse, ParseStream}, 13 | parse2, parse_str, 14 | punctuated::Punctuated, 15 | spanned::Spanned, 16 | token::{Comma, Paren}, 17 | visit::{visit_path, visit_type, Visit}, 18 | Attribute, DeriveInput, Expr, Field, GenericParam, Generics, Ident, Lit, Meta, Path, Result, 19 | Token, Type, WherePredicate, 20 | }; 21 | 22 | macro_rules! bail { 23 | (_, $($arg:tt)*) => { 24 | bail!(::proc_macro2::Span::call_site(), $($arg)*) 25 | }; 26 | ($span:expr, $fmt:literal $(,)?) => { 27 | return ::std::result::Result::Err(::syn::Error::new($span, ::std::format!($fmt))) 28 | }; 29 | ($span:expr, $fmt:literal, $($arg:tt)*) => { 30 | return ::std::result::Result::Err(::syn::Error::new($span, ::std::format!($fmt, $($arg)*))) 31 | }; 32 | } 33 | 34 | pub fn into_macro_output(input: Result) -> proc_macro::TokenStream { 35 | match input { 36 | Ok(s) => s, 37 | Err(e) => e.to_compile_error(), 38 | } 39 | .into() 40 | } 41 | 42 | pub struct Parenthesized { 43 | pub _paren_token: Option, 44 | pub content: T, 45 | } 46 | impl Parse for Parenthesized { 47 | fn parse(input: ParseStream) -> Result { 48 | let content; 49 | let paren_token = Some(parenthesized!(content in input)); 50 | let content = content.parse()?; 51 | Ok(Self { 52 | _paren_token: paren_token, 53 | content, 54 | }) 55 | } 56 | } 57 | impl Deref for Parenthesized { 58 | type Target = T; 59 | fn deref(&self) -> &Self::Target { 60 | &self.content 61 | } 62 | } 63 | 64 | #[derive(Parse)] 65 | pub struct Args(#[parse(terminated)] pub Punctuated); 66 | 67 | impl Args { 68 | pub fn new() -> Self { 69 | Self(Punctuated::new()) 70 | } 71 | pub fn expect_single_value(&self, span: Span) -> Result<&Expr> { 72 | if self.len() != 1 { 73 | bail!( 74 | span, 75 | "expect 1 arguments, but supplied {} arguments.", 76 | self.len() 77 | ); 78 | } 79 | match &self[0] { 80 | Arg::Value(expr) => Ok(expr), 81 | Arg::NameValue { .. } => bail!(span, "expected unnamed argument."), 82 | } 83 | } 84 | } 85 | impl Default for Args { 86 | fn default() -> Self { 87 | Self::new() 88 | } 89 | } 90 | impl Deref for Args { 91 | type Target = Punctuated; 92 | 93 | fn deref(&self) -> &Self::Target { 94 | &self.0 95 | } 96 | } 97 | impl IntoIterator for Args { 98 | type Item = Arg; 99 | type IntoIter = as IntoIterator>::IntoIter; 100 | 101 | fn into_iter(self) -> Self::IntoIter { 102 | self.0.into_iter() 103 | } 104 | } 105 | 106 | #[derive(ToTokens, Parse)] 107 | pub enum Arg { 108 | NameValue { 109 | #[parse(peek, any)] 110 | name: Ident, 111 | #[parse(peek)] 112 | eq_token: Token![=], 113 | value: Expr, 114 | }, 115 | Value(Expr), 116 | } 117 | 118 | pub struct SharpVals { 119 | allow_vals: bool, 120 | allow_self: bool, 121 | pub vals: BTreeMap, 122 | pub self_span: Option, 123 | } 124 | impl SharpVals { 125 | pub fn new(allow_vals: bool, allow_self: bool) -> Self { 126 | Self { 127 | allow_vals, 128 | allow_self, 129 | vals: BTreeMap::new(), 130 | self_span: None, 131 | } 132 | } 133 | pub fn expand(&mut self, input: TokenStream) -> Result { 134 | let mut tokens = Vec::new(); 135 | let mut iter = input.into_iter().peekable(); 136 | while let Some(t) = iter.next() { 137 | match &t { 138 | TokenTree::Group(g) => { 139 | let mut g_new = 140 | TokenTree::Group(Group::new(g.delimiter(), self.expand(g.stream())?)); 141 | g_new.set_span(g.span()); 142 | tokens.push(g_new); 143 | continue; 144 | } 145 | TokenTree::Punct(p) => { 146 | if p.as_char() == '#' && p.spacing() == Spacing::Alone { 147 | if let Some(token) = iter.peek() { 148 | if let Some(key) = FieldKey::try_from_token(token) { 149 | let span = token.span(); 150 | let allow = if &key == "self" { 151 | self.self_span.get_or_insert(span); 152 | self.allow_self 153 | } else { 154 | self.vals.entry(key.clone()).or_insert(span); 155 | self.allow_vals 156 | }; 157 | if !allow { 158 | bail!(span, "cannot use `#{}` in this position.", key); 159 | } 160 | if self.self_span.is_some() { 161 | if let Some(key) = self.vals.keys().next() { 162 | bail!(span, "cannot use both `#self` and `#{}`", key); 163 | } 164 | } 165 | let mut ident = key.to_dummy_ident(); 166 | ident.set_span(span); 167 | tokens.extend(ident.to_token_stream()); 168 | iter.next(); 169 | continue; 170 | } 171 | } 172 | } 173 | } 174 | _ => {} 175 | } 176 | tokens.extend(once(t)); 177 | } 178 | Ok(tokens.into_iter().collect()) 179 | } 180 | 181 | pub fn expand_args(&mut self, meta: &Meta) -> Result { 182 | match meta { 183 | Meta::List(m) => parse2(self.expand(m.tokens.clone())?), 184 | Meta::Path(_) | Meta::NameValue(_) => { 185 | let span = span_in_args(meta); 186 | bail!(span, "expected arguments.") 187 | } 188 | } 189 | } 190 | pub fn expand_args_or_default(&mut self, meta: &Meta) -> Result { 191 | match meta { 192 | Meta::List(m) => parse2(self.expand(m.tokens.clone())?), 193 | Meta::Path(_) => Ok(T::default()), 194 | Meta::NameValue(m) => bail!( 195 | m.eq_token.span(), 196 | "`name = value` style attribute was not supported." 197 | ), 198 | } 199 | } 200 | } 201 | #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] 202 | pub enum FieldKey { 203 | Named(String), 204 | Unnamed(usize), 205 | } 206 | 207 | impl FieldKey { 208 | pub fn from_ident(ident: &Ident) -> Self { 209 | Self::Named(ident.unraw().to_string()) 210 | } 211 | pub fn from_field(idx: usize, field: &Field) -> Self { 212 | if let Some(ident) = &field.ident { 213 | Self::from_ident(ident) 214 | } else { 215 | Self::Unnamed(idx) 216 | } 217 | } 218 | pub fn try_from_token(token: &TokenTree) -> Option { 219 | match token { 220 | TokenTree::Ident(ident) => Some(Self::from_ident(ident)), 221 | TokenTree::Literal(token) => { 222 | if let Lit::Int(lit) = Lit::new(token.clone()) { 223 | if lit.suffix().is_empty() { 224 | if let Ok(idx) = lit.base10_parse() { 225 | return Some(Self::Unnamed(idx)); 226 | } 227 | } 228 | } 229 | None 230 | } 231 | _ => None, 232 | } 233 | } 234 | 235 | pub fn to_dummy_ident(&self) -> Ident { 236 | Ident::new(&format!("_{self}"), Span::call_site()) 237 | } 238 | pub fn to_valid_ident(&self) -> Option { 239 | match self { 240 | Self::Named(name) => to_valid_ident(name).ok(), 241 | Self::Unnamed(..) => None, 242 | } 243 | } 244 | } 245 | impl PartialEq for FieldKey { 246 | fn eq(&self, other: &str) -> bool { 247 | match self { 248 | FieldKey::Named(name) => name == other, 249 | _ => false, 250 | } 251 | } 252 | } 253 | impl std::fmt::Display for FieldKey { 254 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 255 | match self { 256 | Self::Named(name) => name.fmt(f), 257 | Self::Unnamed(idx) => idx.fmt(f), 258 | } 259 | } 260 | } 261 | 262 | pub struct GenericParamSet { 263 | idents: HashSet, 264 | } 265 | 266 | impl GenericParamSet { 267 | pub fn new(generics: &Generics) -> Self { 268 | let mut idents = HashSet::new(); 269 | for p in &generics.params { 270 | match p { 271 | GenericParam::Type(t) => { 272 | idents.insert(t.ident.unraw()); 273 | } 274 | GenericParam::Const(t) => { 275 | idents.insert(t.ident.unraw()); 276 | } 277 | _ => {} 278 | } 279 | } 280 | Self { idents } 281 | } 282 | fn contains(&self, ident: &Ident) -> bool { 283 | self.idents.contains(&ident.unraw()) 284 | } 285 | 286 | pub fn contains_in_type(&self, ty: &Type) -> bool { 287 | struct Visitor<'a> { 288 | generics: &'a GenericParamSet, 289 | result: bool, 290 | } 291 | impl<'ast> Visit<'ast> for Visitor<'_> { 292 | fn visit_path(&mut self, i: &'ast syn::Path) { 293 | if i.leading_colon.is_none() { 294 | if let Some(s) = i.segments.iter().next() { 295 | if self.generics.contains(&s.ident) { 296 | self.result = true; 297 | } 298 | } 299 | } 300 | visit_path(self, i); 301 | } 302 | } 303 | let mut visitor = Visitor { 304 | generics: self, 305 | result: false, 306 | }; 307 | visit_type(&mut visitor, ty); 308 | visitor.result 309 | } 310 | } 311 | 312 | pub fn impl_trait( 313 | input: &DeriveInput, 314 | trait_path: &Path, 315 | wheres: &[WherePredicate], 316 | contents: TokenStream, 317 | ) -> TokenStream { 318 | let ty = &input.ident; 319 | let (impl_g, ty_g, where_clause) = input.generics.split_for_impl(); 320 | let mut wheres = wheres.to_vec(); 321 | if let Some(where_clause) = where_clause { 322 | wheres.extend(where_clause.predicates.iter().cloned()); 323 | } 324 | let where_clause = if wheres.is_empty() { 325 | quote! {} 326 | } else { 327 | quote! { where #(#wheres,)*} 328 | }; 329 | quote! { 330 | #[automatically_derived] 331 | impl #impl_g #trait_path for #ty #ty_g #where_clause { 332 | #contents 333 | } 334 | } 335 | } 336 | pub fn impl_trait_result( 337 | input: &DeriveInput, 338 | trait_path: &Path, 339 | wheres: &[WherePredicate], 340 | contents: TokenStream, 341 | dump: bool, 342 | ) -> Result { 343 | let ts = impl_trait(input, trait_path, wheres, contents); 344 | if dump { 345 | panic!("macro result: \n{ts}"); 346 | } 347 | Ok(ts) 348 | } 349 | 350 | pub fn to_valid_ident(s: &str) -> Result { 351 | if let Ok(ident) = parse_str(s) { 352 | Ok(ident) 353 | } else { 354 | parse_str(&format!("r#{s}")) 355 | } 356 | } 357 | 358 | pub fn parse_from_attrs(attrs: &[Attribute], name: &str) -> Result { 359 | let mut a = None; 360 | for attr in attrs { 361 | if attr.path().is_ident(name) { 362 | if a.is_some() { 363 | bail!(attr.span(), "attribute `{}` can specified only once", name); 364 | } 365 | a = Some(attr); 366 | } 367 | } 368 | if let Some(a) = a { 369 | a.parse_args() 370 | } else { 371 | Ok(T::default()) 372 | } 373 | } 374 | 375 | pub fn span_in_args(meta: &Meta) -> Span { 376 | match meta { 377 | Meta::Path(_) => meta.span(), 378 | Meta::List(m) => m.delimiter.span().span(), 379 | Meta::NameValue(m) => m.value.span(), 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /tests/arbitrary.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::derive_partial_eq_without_eq)] 2 | mod test_helpers; 3 | 4 | use proptest::{ 5 | arbitrary::{any, any_with, Arbitrary}, 6 | collection::size_range, 7 | prop_oneof, 8 | strategy::{Just, LazyJust, Strategy}, 9 | }; 10 | use std::fmt::Debug; 11 | use test_helpers::*; 12 | use test_strategy::Arbitrary; 13 | 14 | #[test] 15 | fn unit_struct() { 16 | #[derive(Arbitrary, Debug, PartialEq)] 17 | struct TestStruct; 18 | assert_arbitrary(LazyJust::new(|| TestStruct)); 19 | } 20 | #[test] 21 | fn tuple_struct_no_field() { 22 | #[derive(Arbitrary, Debug, PartialEq)] 23 | struct TestStruct(); 24 | assert_arbitrary(LazyJust::new(TestStruct)); 25 | } 26 | #[test] 27 | fn struct_no_field() { 28 | #[derive(Arbitrary, Debug, PartialEq)] 29 | struct TestStruct {} 30 | assert_arbitrary(LazyJust::new(|| TestStruct {})); 31 | } 32 | 33 | #[test] 34 | fn struct_no_attr_field() { 35 | #[derive(Arbitrary, Debug, PartialEq)] 36 | struct TestStruct { 37 | x: u16, 38 | } 39 | assert_arbitrary(any::().prop_map(|x| TestStruct { x })); 40 | } 41 | 42 | #[test] 43 | fn struct_no_attr_field_x2() { 44 | #[derive(Arbitrary, Debug, PartialEq)] 45 | struct TestStruct { 46 | x: u16, 47 | y: u8, 48 | } 49 | 50 | assert_arbitrary((any::(), any::()).prop_map(|(x, y)| TestStruct { x, y })); 51 | } 52 | 53 | #[test] 54 | fn struct_field_raw_ident() { 55 | #[derive(Arbitrary, Debug, PartialEq)] 56 | struct TestStruct { 57 | r#x: u16, 58 | } 59 | assert_arbitrary(any::().prop_map(|x| TestStruct { x })); 60 | } 61 | 62 | #[test] 63 | fn struct_field_raw_keyword_ident() { 64 | #[derive(Arbitrary, Debug, PartialEq)] 65 | struct TestStruct { 66 | r#fn: u16, 67 | } 68 | assert_arbitrary(any::().prop_map(|r#fn| TestStruct { r#fn })); 69 | } 70 | 71 | #[test] 72 | fn struct_over_12_field() { 73 | #[derive(Arbitrary, Debug, PartialEq)] 74 | struct TestStruct { 75 | a0: u32, 76 | a1: u32, 77 | a2: u32, 78 | a3: u32, 79 | a4: u32, 80 | a5: u32, 81 | a6: u32, 82 | a7: u32, 83 | a8: u32, 84 | a9: u32, 85 | a10: u32, 86 | a11: u32, 87 | a12: u32, 88 | } 89 | } 90 | 91 | #[test] 92 | fn tuple_struct_no_attr_field() { 93 | #[derive(Arbitrary, Debug, PartialEq)] 94 | struct TestStruct(u16); 95 | assert_arbitrary(any::().prop_map(TestStruct)); 96 | } 97 | #[test] 98 | fn tuple_struct_no_attr_field_x2() { 99 | #[derive(Arbitrary, Debug, PartialEq)] 100 | struct TestStruct(u16, u8); 101 | assert_arbitrary((any::(), any::()).prop_map(|(x, y)| TestStruct(x, y))); 102 | } 103 | 104 | #[test] 105 | fn strategy_struct() { 106 | #[derive(Arbitrary, Debug, PartialEq)] 107 | struct TestStruct { 108 | #[strategy(1..16u16)] 109 | x: u16, 110 | } 111 | assert_arbitrary((1..16u16).prop_map(|x| TestStruct { x })); 112 | } 113 | 114 | #[test] 115 | fn strategy_struct_field_x2() { 116 | #[derive(Arbitrary, Debug, PartialEq)] 117 | struct TestStruct { 118 | #[strategy(1..16u16)] 119 | x: u16, 120 | 121 | #[strategy(10..20u8)] 122 | y: u8, 123 | } 124 | assert_arbitrary((1..16u16, 10..20u8).prop_map(|(x, y)| TestStruct { x, y })); 125 | } 126 | 127 | #[test] 128 | fn strategy_rank2() { 129 | #[derive(Arbitrary, Debug, PartialEq)] 130 | struct TestStruct { 131 | #[strategy(1..10u32)] 132 | x: u32, 133 | #[strategy(0..#x)] 134 | y: u32, 135 | } 136 | assert_arbitrary( 137 | (1..10u32) 138 | .prop_flat_map(|x| (0..x, Just(x))) 139 | .prop_map(|(y, x)| TestStruct { x, y }), 140 | ); 141 | } 142 | 143 | #[test] 144 | fn strategy_rank3() { 145 | #[derive(Arbitrary, Debug, PartialEq)] 146 | struct TestStruct { 147 | #[strategy(1..10u32)] 148 | x: u32, 149 | #[strategy(0..#x)] 150 | y: u32, 151 | #[strategy(0..#y*2+1)] 152 | z: u32, 153 | } 154 | assert_arbitrary( 155 | (1..10u32) 156 | .prop_flat_map(|x| (0..x, Just(x))) 157 | .prop_flat_map(|(y, x)| (0..y * 2 + 1, Just(x), Just(y))) 158 | .prop_map(|(z, x, y)| TestStruct { x, y, z }), 159 | ); 160 | } 161 | 162 | #[test] 163 | fn strategy_underline() { 164 | #[derive(Arbitrary, Debug, PartialEq)] 165 | struct TestStruct { 166 | #[strategy(1..16u16)] 167 | _x: u16, 168 | } 169 | } 170 | 171 | #[test] 172 | fn strategy_rev() { 173 | #[derive(Arbitrary, Debug, PartialEq)] 174 | struct TestStruct { 175 | #[strategy(0..#x)] 176 | y: u32, 177 | #[strategy(1..10u32)] 178 | x: u32, 179 | } 180 | assert_arbitrary( 181 | (1..10u32) 182 | .prop_flat_map(|x| (0..x, Just(x))) 183 | .prop_map(|(y, x)| TestStruct { x, y }), 184 | ); 185 | } 186 | 187 | #[test] 188 | fn strategy_tuple_struct() { 189 | #[derive(Arbitrary, Debug, PartialEq)] 190 | struct TestStruct(#[strategy(1..10u32)] u32, #[strategy(0..#0)] u32); 191 | assert_arbitrary( 192 | (1..10u32) 193 | .prop_flat_map(|x| (0..x, Just(x))) 194 | .prop_map(|(y, x)| TestStruct(x, y)), 195 | ); 196 | } 197 | 198 | #[test] 199 | fn strategy_multiple_dependency() { 200 | #[derive(Arbitrary, Debug, PartialEq)] 201 | struct TestStruct { 202 | #[strategy(0..10)] 203 | x: i32, 204 | #[strategy(0..20)] 205 | y: i32, 206 | #[strategy(0..#x*#y+1)] 207 | z: i32, 208 | } 209 | assert_arbitrary( 210 | (0..10, 0..20) 211 | .prop_flat_map(|(x, y)| (0..x * y + 1, Just(x), Just(y))) 212 | .prop_map(|(z, x, y)| TestStruct { x, y, z }), 213 | ); 214 | } 215 | 216 | #[test] 217 | fn strategy_cross_dependency() { 218 | #[derive(Arbitrary, Debug, PartialEq)] 219 | struct TestStruct { 220 | #[strategy(0..10)] 221 | a: i32, 222 | #[strategy(0..20)] 223 | b: i32, 224 | #[strategy(0..30)] 225 | c: i32, 226 | #[strategy(0..#a * #b + 1)] 227 | x: i32, 228 | #[strategy(0..#b + #c + 10)] 229 | y: i32, 230 | } 231 | assert_arbitrary( 232 | (0..10, 0..20, 0..30) 233 | .prop_flat_map(|(a, b, c)| (0..a * b + 1, 0..b + c + 10, Just(a), Just(b), Just(c))) 234 | .prop_map(|(x, y, a, b, c)| TestStruct { a, b, c, x, y }), 235 | ); 236 | } 237 | 238 | #[test] 239 | fn strategy_by_ref() { 240 | #[derive(Arbitrary, Debug, PartialEq)] 241 | struct TestStruct { 242 | #[by_ref] 243 | #[strategy(1..10u32)] 244 | x: u32, 245 | #[strategy(0..*#x)] 246 | y: u32, 247 | } 248 | assert_arbitrary( 249 | (1..10u32) 250 | .prop_flat_map(|x| (0..x, Just(x))) 251 | .prop_map(|(y, x)| TestStruct { x, y }), 252 | ); 253 | } 254 | 255 | #[test] 256 | fn strategy_raw_keyword_field() { 257 | #[derive(Arbitrary, Debug, PartialEq)] 258 | struct TestStruct { 259 | #[strategy(1..10u32)] 260 | r#fn: u32, 261 | } 262 | assert_arbitrary((1..10u32).prop_map(|r#fn| TestStruct { r#fn })); 263 | } 264 | 265 | #[test] 266 | fn strategy_raw_keyword_sharp_val() { 267 | #[derive(Arbitrary, Debug, PartialEq)] 268 | struct TestStruct { 269 | #[strategy(1..10u32)] 270 | r#fn: u32, 271 | #[strategy(0..#fn)] 272 | y: u32, 273 | } 274 | assert_arbitrary( 275 | (1..10u32) 276 | .prop_flat_map(|x| (0..x, Just(x))) 277 | .prop_map(|(y, r#fn)| TestStruct { r#fn, y }), 278 | ); 279 | } 280 | 281 | #[test] 282 | fn any_struct_field() { 283 | #[derive(Arbitrary, Debug, PartialEq)] 284 | struct TestStruct { 285 | #[any] 286 | x: u16, 287 | } 288 | assert_arbitrary(any::().prop_map(|x| TestStruct { x })); 289 | } 290 | 291 | #[test] 292 | fn any_with_args_expr() { 293 | #[derive(Arbitrary, Debug, PartialEq)] 294 | struct TestStruct { 295 | #[any(size_range(0..16).lift())] 296 | x: Vec, 297 | } 298 | assert_arbitrary(any_with::>(size_range(0..16).lift()).prop_map(|x| TestStruct { x })); 299 | } 300 | 301 | mod sub_mod { 302 | 303 | #[allow(dead_code)] 304 | #[derive(Default)] 305 | pub struct TestArgsNoConstruct { 306 | a: u32, 307 | } 308 | impl TestArgsNoConstruct { 309 | pub fn new() -> Self { 310 | Self { a: 0 } 311 | } 312 | } 313 | } 314 | 315 | #[test] 316 | fn any_with_args_expr_private_constructor() { 317 | #[derive(Arbitrary, Debug)] 318 | #[arbitrary(args = sub_mod::TestArgsNoConstruct)] 319 | struct Inner { 320 | #[allow(dead_code)] 321 | x: u32, 322 | } 323 | 324 | #[derive(Arbitrary, Debug)] 325 | struct TestInput { 326 | #[any(sub_mod::TestArgsNoConstruct::new())] 327 | #[allow(dead_code)] 328 | inner: Inner, 329 | } 330 | } 331 | 332 | #[test] 333 | fn any_with_args_struct_field() { 334 | #[derive(Default)] 335 | struct TestArgs { 336 | a_max: i32, 337 | } 338 | 339 | #[derive(Arbitrary, Debug, PartialEq)] 340 | #[arbitrary(args = TestArgs)] 341 | struct Inner { 342 | #[strategy(0..args.a_max)] 343 | a: i32, 344 | } 345 | 346 | #[derive(Arbitrary, Debug, PartialEq)] 347 | struct Outer { 348 | #[any(a_max = 50)] 349 | inner: Inner, 350 | } 351 | assert_arbitrary((0..50).prop_map(|a| Outer { inner: Inner { a } })); 352 | } 353 | 354 | #[test] 355 | fn any_with_args_enum_field() { 356 | #[derive(Default)] 357 | struct TestArgs { 358 | a_max: i32, 359 | } 360 | 361 | #[derive(Arbitrary, Debug, PartialEq)] 362 | #[arbitrary(args = TestArgs)] 363 | enum Inner { 364 | A { 365 | #[strategy(0..args.a_max)] 366 | a: i32, 367 | }, 368 | } 369 | 370 | #[derive(Arbitrary, Debug, PartialEq)] 371 | struct Outer { 372 | #[any(a_max = 50)] 373 | inner: Inner, 374 | } 375 | assert_arbitrary((0..50).prop_map(|a| Outer { 376 | inner: Inner::A { a }, 377 | })); 378 | } 379 | 380 | #[test] 381 | fn any_with_args_field_x2() { 382 | #[derive(Default)] 383 | struct TestArgs { 384 | a_max1: i32, 385 | a_max2: i32, 386 | } 387 | 388 | #[derive(Arbitrary, Debug, PartialEq)] 389 | #[arbitrary(args = TestArgs)] 390 | struct Inner { 391 | #[strategy(0..args.a_max1 + args.a_max2)] 392 | a: i32, 393 | } 394 | 395 | #[derive(Arbitrary, Debug, PartialEq)] 396 | struct Outer { 397 | #[any(a_max1 = 50, a_max2 = 10)] 398 | inner: Inner, 399 | } 400 | assert_arbitrary((0..60).prop_map(|a| Outer { inner: Inner { a } })); 401 | } 402 | 403 | #[test] 404 | fn any_with_args_expr_field() { 405 | #[derive(Default)] 406 | struct TestArgs { 407 | a_max1: i32, 408 | a_max2: i32, 409 | } 410 | impl TestArgs { 411 | fn new() -> Self { 412 | Self { 413 | a_max1: 10, 414 | a_max2: 20, 415 | } 416 | } 417 | } 418 | 419 | #[derive(Arbitrary, Debug, PartialEq)] 420 | #[arbitrary(args = TestArgs)] 421 | struct Inner { 422 | #[strategy(0..args.a_max1 + args.a_max2)] 423 | a: i32, 424 | } 425 | 426 | #[derive(Arbitrary, Debug, PartialEq)] 427 | struct Outer { 428 | #[any(TestArgs::new(), a_max2 = 30)] 429 | inner: Inner, 430 | } 431 | assert_arbitrary((0..40).prop_map(|a| Outer { inner: Inner { a } })); 432 | } 433 | 434 | #[test] 435 | fn any_with_keyword() { 436 | #[derive(Default)] 437 | struct TestArgs { 438 | r#fn: i32, 439 | } 440 | 441 | #[derive(Arbitrary, Debug, PartialEq)] 442 | #[arbitrary(args = TestArgs)] 443 | struct Inner { 444 | #[strategy(0..args.r#fn)] 445 | a: i32, 446 | } 447 | 448 | #[derive(Arbitrary, Debug, PartialEq)] 449 | struct Outer { 450 | #[any(fn = 50)] 451 | inner: Inner, 452 | } 453 | assert_arbitrary((0..50).prop_map(|a| Outer { inner: Inner { a } })); 454 | } 455 | 456 | #[test] 457 | fn enum_unit_variant_1() { 458 | #[derive(Arbitrary, Debug, PartialEq)] 459 | enum TestEnum { 460 | X, 461 | } 462 | assert_arbitrary(LazyJust::new(|| TestEnum::X)); 463 | } 464 | 465 | #[test] 466 | fn enum_unit_variant_2() { 467 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 468 | enum TestEnum { 469 | X, 470 | Y, 471 | } 472 | assert_arbitrary(prop_oneof![Just(TestEnum::X), Just(TestEnum::Y)]); 473 | } 474 | 475 | #[test] 476 | fn enum_weight() { 477 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 478 | enum TestEnum { 479 | #[weight(1)] 480 | X, 481 | #[weight(2)] 482 | Y, 483 | } 484 | assert_arbitrary(prop_oneof![1=>Just(TestEnum::X), 2=>Just(TestEnum::Y)]); 485 | } 486 | 487 | #[test] 488 | fn enum_weight_0() { 489 | #[derive(Debug, PartialEq, Clone)] 490 | struct NotArbitrary; 491 | 492 | #[allow(dead_code)] 493 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 494 | enum TestEnum { 495 | X, 496 | #[weight(0)] 497 | Y(NotArbitrary), 498 | } 499 | 500 | assert_arbitrary(Just(TestEnum::X)); 501 | } 502 | 503 | #[test] 504 | fn filter_struct_field_fn() { 505 | #[derive(Arbitrary, Debug, PartialEq)] 506 | struct TestStruct { 507 | #[strategy(1..10u32)] 508 | #[filter(is_even)] 509 | x: u32, 510 | } 511 | fn is_even(value: &u32) -> bool { 512 | value % 2 == 0 513 | } 514 | assert_arbitrary( 515 | (1..10u32) 516 | .prop_filter("is_even", is_even) 517 | .prop_map(|x| TestStruct { x }), 518 | ); 519 | } 520 | 521 | #[test] 522 | fn filter_struct_field_2() { 523 | #[derive(Arbitrary, Debug, PartialEq)] 524 | struct TestStruct { 525 | #[strategy(1..10u32)] 526 | #[filter(is_even)] 527 | x: u32, 528 | 529 | #[strategy(1..10u32)] 530 | #[filter(is_odd)] 531 | y: u32, 532 | } 533 | fn is_even(value: &u32) -> bool { 534 | value % 2 == 0 535 | } 536 | fn is_odd(value: &u32) -> bool { 537 | value % 2 != 0 538 | } 539 | assert_arbitrary( 540 | ( 541 | (1..10u32).prop_filter("is_even", is_even), 542 | (1..10u32).prop_filter("is_odd", is_odd), 543 | ) 544 | .prop_map(|(x, y)| TestStruct { x, y }), 545 | ); 546 | } 547 | 548 | #[test] 549 | fn filter_struct_filed_independent_and_dependent() { 550 | #[derive(Arbitrary, Debug, PartialEq)] 551 | struct TestStruct { 552 | #[strategy(0..10u32)] 553 | #[filter(|x| x % 3 == 0)] 554 | a: u32, 555 | 556 | #[strategy(0..10u32)] 557 | #[filter(|x| x % 3 == 1)] 558 | b: u32, 559 | 560 | #[strategy(0..=#b)] 561 | #[filter(|x| x % 3 == 2)] 562 | c: u32, 563 | } 564 | 565 | let a = (0..10u32).prop_filter("", |&a| a % 3 == 0); 566 | let bc = (0..10u32) 567 | .prop_filter("", |&b| b % 3 == 1) 568 | .prop_flat_map(|b| (Just(b), (0..=b))) 569 | .prop_filter("", |(_b, c)| c % 3 == 2); 570 | let abc = (a, bc).prop_map(|(a, (b, c))| TestStruct { a, b, c }); 571 | assert_arbitrary(abc); 572 | } 573 | 574 | #[test] 575 | fn filter_struct_field_fn_dependency() { 576 | #[derive(Arbitrary, Debug, PartialEq)] 577 | struct TestStruct { 578 | a: u32, 579 | #[filter(is_larger_than(#a))] 580 | b: u32, 581 | } 582 | 583 | fn is_larger_than(t: u32) -> impl Fn(&u32) -> bool { 584 | move |&value| value > t 585 | } 586 | assert_arbitrary( 587 | (any::(), any::()) 588 | .prop_filter("is_larger_than(a)", |&(a, b)| is_larger_than(a)(&b)) 589 | .prop_map(|(a, b)| TestStruct { a, b }), 590 | ); 591 | } 592 | 593 | #[test] 594 | fn filter_struct_field_fn_dependency_closure() { 595 | #[derive(Arbitrary, Debug, PartialEq)] 596 | struct TestStruct { 597 | a: u32, 598 | #[filter(|&b| b > #a)] 599 | b: u32, 600 | } 601 | 602 | assert_arbitrary( 603 | (any::(), any::()) 604 | .prop_filter("b > a", |&(a, b)| b > a) 605 | .prop_map(|(a, b)| TestStruct { a, b }), 606 | ); 607 | } 608 | 609 | #[test] 610 | fn filter_struct_field_sharp_val() { 611 | #[derive(Arbitrary, Debug, PartialEq)] 612 | struct TestStruct { 613 | #[strategy(1..10u32)] 614 | #[filter(is_even(&#x))] 615 | x: u32, 616 | } 617 | fn is_even(value: &u32) -> bool { 618 | value % 2 == 0 619 | } 620 | assert_arbitrary( 621 | (1..10u32) 622 | .prop_filter("is_even", is_even) 623 | .prop_map(|x| TestStruct { x }), 624 | ); 625 | } 626 | 627 | #[test] 628 | fn filter_tuple_struct_field_sharp_val() { 629 | #[derive(Arbitrary, Debug, PartialEq)] 630 | struct TestStruct( 631 | #[strategy(1..10u32)] 632 | #[filter(is_even(�))] 633 | u32, 634 | ); 635 | fn is_even(value: &u32) -> bool { 636 | value % 2 == 0 637 | } 638 | assert_arbitrary( 639 | (1..10u32) 640 | .prop_filter("is_even", is_even) 641 | .prop_map(TestStruct), 642 | ); 643 | } 644 | 645 | #[test] 646 | fn filter_struct_field_closure() { 647 | #[derive(Arbitrary, Debug, PartialEq)] 648 | struct TestStruct { 649 | #[strategy(1..10u32)] 650 | #[filter(|val| val % 2 == 0)] 651 | x: u32, 652 | } 653 | assert_arbitrary( 654 | (1..10u32) 655 | .prop_filter("is_even", |val| val % 2 == 0) 656 | .prop_map(|x| TestStruct { x }), 657 | ); 658 | } 659 | 660 | #[test] 661 | fn filter_struct_sharp_vals() { 662 | #[derive(Arbitrary, Debug, PartialEq)] 663 | #[filter(#x > #y)] 664 | struct TestStruct { 665 | x: u32, 666 | y: u32, 667 | } 668 | assert_arbitrary( 669 | (any::(), any::()) 670 | .prop_filter("x > y", |&(x, y)| x > y) 671 | .prop_map(|(x, y)| TestStruct { x, y }), 672 | ); 673 | } 674 | 675 | #[test] 676 | fn filter_struct_sharp_self() { 677 | #[derive(Arbitrary, Debug, PartialEq)] 678 | #[filter(#self.x > #self.y)] 679 | struct TestStruct { 680 | x: u32, 681 | y: u32, 682 | } 683 | assert_arbitrary( 684 | (any::(), any::()) 685 | .prop_filter("x > y", |&(x, y)| x > y) 686 | .prop_map(|(x, y)| TestStruct { x, y }), 687 | ); 688 | } 689 | 690 | #[test] 691 | fn filter_struct_fn() { 692 | #[derive(Arbitrary, Debug, PartialEq)] 693 | #[filter(|s| s.x > s.y)] 694 | struct TestStruct { 695 | x: u32, 696 | y: u32, 697 | } 698 | assert_arbitrary( 699 | (any::(), any::()) 700 | .prop_filter("x > y", |&(x, y)| x > y) 701 | .prop_map(|(x, y)| TestStruct { x, y }), 702 | ); 703 | } 704 | 705 | #[test] 706 | fn filter_enum_fn() { 707 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 708 | #[filter(Self::is_valid)] 709 | enum TestEnum { 710 | A(i32), 711 | B(i32), 712 | } 713 | impl TestEnum { 714 | fn is_valid(&self) -> bool { 715 | match self { 716 | Self::A(x) => x % 2 == 0, 717 | Self::B(x) => x % 2 != 0, 718 | } 719 | } 720 | } 721 | 722 | assert_arbitrary( 723 | prop_oneof![ 724 | any::().prop_map(TestEnum::A), 725 | any::().prop_map(TestEnum::B), 726 | ] 727 | .prop_filter("is_valid", TestEnum::is_valid), 728 | ); 729 | } 730 | 731 | #[test] 732 | fn filter_enum_sharp_self() { 733 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 734 | #[filter(#self.is_valid())] 735 | enum TestEnum { 736 | A(i32), 737 | B(i32), 738 | } 739 | impl TestEnum { 740 | fn is_valid(&self) -> bool { 741 | match self { 742 | Self::A(x) => x % 2 == 0, 743 | Self::B(x) => x % 2 != 0, 744 | } 745 | } 746 | } 747 | assert_arbitrary( 748 | prop_oneof![ 749 | any::().prop_map(TestEnum::A), 750 | any::().prop_map(TestEnum::B), 751 | ] 752 | .prop_filter("is_valid", TestEnum::is_valid), 753 | ); 754 | } 755 | 756 | #[test] 757 | fn filter_variant() { 758 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 759 | enum TestEnum { 760 | #[filter(#x > #y)] 761 | A { 762 | x: i32, 763 | y: i32, 764 | }, 765 | B(i32), 766 | } 767 | assert_arbitrary(prop_oneof![ 768 | (any::(), any::()) 769 | .prop_filter("x > y", |&(x, y)| x > y) 770 | .prop_map(|(x, y)| TestEnum::A { x, y }), 771 | any::().prop_map(TestEnum::B), 772 | ]); 773 | } 774 | 775 | #[test] 776 | fn filter_raw_keyword() { 777 | #[derive(Arbitrary, Debug, PartialEq)] 778 | #[filter(#fn > #y)] 779 | struct TestStruct { 780 | r#fn: u32, 781 | y: u32, 782 | } 783 | assert_arbitrary( 784 | (any::(), any::()) 785 | .prop_filter("fn > y", |&(r#fn, y)| r#fn > y) 786 | .prop_map(|(r#fn, y)| TestStruct { r#fn, y }), 787 | ); 788 | } 789 | 790 | #[test] 791 | fn filter_with_whence() { 792 | #[derive(Arbitrary, Debug, PartialEq)] 793 | struct TestStruct { 794 | #[strategy(1..10u32)] 795 | #[filter("is_even", is_even)] 796 | x: u32, 797 | } 798 | fn is_even(value: &u32) -> bool { 799 | value % 2 == 0 800 | } 801 | assert_arbitrary( 802 | (1..10u32) 803 | .prop_filter("is_even", is_even) 804 | .prop_map(|x| TestStruct { x }), 805 | ); 806 | } 807 | 808 | #[test] 809 | fn filter_with_by_ref() { 810 | #[derive(Arbitrary, Debug, PartialEq)] 811 | #[filter(#x > *#y)] 812 | struct TestStruct { 813 | x: u32, 814 | #[by_ref] 815 | y: u32, 816 | } 817 | assert_arbitrary( 818 | (any::(), any::()) 819 | .prop_filter("x > y", |&(x, y)| x > y) 820 | .prop_map(|(x, y)| TestStruct { x, y }), 821 | ); 822 | } 823 | 824 | #[test] 825 | fn args_struct() { 826 | #[derive(Default)] 827 | struct TestArgs { 828 | x_max: u32, 829 | } 830 | #[derive(Arbitrary, Debug, PartialEq)] 831 | #[arbitrary(args = TestArgs)] 832 | struct TestStruct { 833 | #[strategy(0..args.x_max)] 834 | x: u32, 835 | } 836 | } 837 | 838 | #[test] 839 | fn args_with_strategy_sharp_val() { 840 | #[derive(Default)] 841 | struct TestArgs { 842 | y: i32, 843 | } 844 | #[derive(Arbitrary, Debug, PartialEq)] 845 | #[arbitrary(args = TestArgs)] 846 | struct TestStruct { 847 | #[strategy(0..10)] 848 | x: i32, 849 | 850 | #[strategy(0..#x + args.y)] 851 | y: i32, 852 | } 853 | } 854 | #[test] 855 | fn args_with_strategy_sharp_val_x2() { 856 | #[derive(Default)] 857 | struct TestArgs { 858 | a: i32, 859 | } 860 | #[derive(Arbitrary, Debug, PartialEq)] 861 | #[arbitrary(args = TestArgs)] 862 | struct TestStruct { 863 | #[strategy(0..10)] 864 | x: i32, 865 | 866 | #[strategy(0..#x + args.a)] 867 | y: i32, 868 | 869 | #[strategy(0..#y + args.a)] 870 | z: i32, 871 | } 872 | } 873 | 874 | #[test] 875 | fn args_with_struct_filter_sharp_val() { 876 | #[derive(Default)] 877 | struct TestArgs { 878 | m: i32, 879 | } 880 | #[derive(Arbitrary, Debug, PartialEq)] 881 | #[arbitrary(args = TestArgs)] 882 | #[filter(#x % args.m != 0)] 883 | struct TestStruct { 884 | x: i32, 885 | } 886 | } 887 | 888 | #[test] 889 | fn args_with_struct_filter_sharp_val_x2() { 890 | #[derive(Default)] 891 | struct TestArgs { 892 | m: i32, 893 | } 894 | #[derive(Arbitrary, Debug, PartialEq)] 895 | #[arbitrary(args = TestArgs)] 896 | #[filter(#x % args.m != 0)] 897 | #[filter(#x % args.m != 1)] 898 | struct TestStruct { 899 | x: i32, 900 | } 901 | } 902 | 903 | #[test] 904 | fn args_with_struct_filter_sharp_self() { 905 | #[derive(Default)] 906 | struct TestArgs { 907 | m: i32, 908 | } 909 | #[derive(Arbitrary, Debug, PartialEq)] 910 | #[arbitrary(args = TestArgs)] 911 | #[filter(#self.x % args.m != 0)] 912 | struct TestStruct { 913 | x: i32, 914 | } 915 | } 916 | 917 | #[test] 918 | fn args_with_struct_filter_sharp_self_x2() { 919 | #[derive(Default)] 920 | struct TestArgs { 921 | m: i32, 922 | } 923 | #[derive(Arbitrary, Debug, PartialEq)] 924 | #[arbitrary(args = TestArgs)] 925 | #[filter(#self.x % args.m != 0)] 926 | #[filter(#self.x % args.m != 1)] 927 | struct TestStruct { 928 | x: i32, 929 | } 930 | } 931 | 932 | #[test] 933 | fn args_with_struct_filter_fn() { 934 | #[derive(Default)] 935 | struct TestArgs { 936 | m: i32, 937 | } 938 | #[derive(Arbitrary, Debug, PartialEq)] 939 | #[arbitrary(args = TestArgs)] 940 | #[filter(is_valid_fn(args.m))] 941 | struct TestStruct { 942 | x: i32, 943 | } 944 | 945 | fn is_valid_fn(_: i32) -> impl Fn(&TestStruct) -> bool { 946 | |_| true 947 | } 948 | } 949 | 950 | #[test] 951 | fn args_with_struct_filter_fn_x2() { 952 | #[derive(Default)] 953 | struct TestArgs { 954 | m: i32, 955 | } 956 | #[derive(Arbitrary, Debug, PartialEq)] 957 | #[arbitrary(args = TestArgs)] 958 | #[filter(is_valid_fn(args.m))] 959 | #[filter(is_valid_fn(args.m + 1))] 960 | struct TestStruct { 961 | x: i32, 962 | } 963 | 964 | fn is_valid_fn(_: i32) -> impl Fn(&TestStruct) -> bool { 965 | |_| true 966 | } 967 | } 968 | 969 | #[test] 970 | fn args_with_enum_filter_sharp_val() { 971 | #[derive(Default)] 972 | struct TestArgs { 973 | m: i32, 974 | } 975 | #[derive(Arbitrary, Debug, PartialEq)] 976 | #[arbitrary(args = TestArgs)] 977 | #[filter(#self.is_valid(args.m))] 978 | enum TestEnum { 979 | A { x: i32 }, 980 | B, 981 | } 982 | 983 | impl TestEnum { 984 | fn is_valid(&self, m: i32) -> bool { 985 | match self { 986 | Self::A { x } => x % m != 0, 987 | Self::B => true, 988 | } 989 | } 990 | } 991 | } 992 | 993 | #[test] 994 | fn args_with_enum_filter_sharp_val_x2() { 995 | #[derive(Default)] 996 | struct TestArgs { 997 | m: i32, 998 | } 999 | #[derive(Arbitrary, Debug, PartialEq)] 1000 | #[arbitrary(args = TestArgs)] 1001 | #[filter(#self.is_valid(args.m))] 1002 | #[filter(#self.is_valid(args.m + 1))] 1003 | enum TestEnum { 1004 | A { x: i32 }, 1005 | B, 1006 | } 1007 | 1008 | impl TestEnum { 1009 | fn is_valid(&self, m: i32) -> bool { 1010 | match self { 1011 | Self::A { x } => x % m != 0, 1012 | Self::B => true, 1013 | } 1014 | } 1015 | } 1016 | } 1017 | 1018 | #[test] 1019 | fn args_with_variant_filter_sharp_val() { 1020 | #[derive(Default)] 1021 | struct TestArgs { 1022 | m: i32, 1023 | } 1024 | #[derive(Arbitrary, Debug, PartialEq)] 1025 | #[arbitrary(args = TestArgs)] 1026 | enum TestEnum { 1027 | #[filter(#x % args.m != 0)] 1028 | A { 1029 | x: i32, 1030 | }, 1031 | B, 1032 | } 1033 | } 1034 | 1035 | #[test] 1036 | fn args_with_variant_filter_sharp_val_x2() { 1037 | #[derive(Default)] 1038 | struct TestArgs { 1039 | m: i32, 1040 | } 1041 | #[derive(Arbitrary, Debug, PartialEq)] 1042 | #[arbitrary(args = TestArgs)] 1043 | enum TestEnum { 1044 | #[filter(#x % args.m != 0)] 1045 | #[filter(#x % args.m != 1)] 1046 | A { 1047 | x: i32, 1048 | }, 1049 | B, 1050 | } 1051 | } 1052 | 1053 | #[test] 1054 | fn args_with_field_filter_sharp_val() { 1055 | #[derive(Default)] 1056 | struct TestArgs { 1057 | m: i32, 1058 | } 1059 | #[derive(Arbitrary, Debug, PartialEq)] 1060 | #[arbitrary(args = TestArgs)] 1061 | struct TestStruct { 1062 | #[filter(#x % args.m != 0)] 1063 | x: i32, 1064 | } 1065 | } 1066 | 1067 | #[test] 1068 | fn args_with_field_filter_sharp_val_x2() { 1069 | #[derive(Default)] 1070 | struct TestArgs { 1071 | m: i32, 1072 | } 1073 | #[derive(Arbitrary, Debug, PartialEq)] 1074 | #[arbitrary(args = TestArgs)] 1075 | struct TestStruct { 1076 | #[filter(#x % args.m != 0)] 1077 | #[filter(#x % args.m != 1)] 1078 | x: i32, 1079 | } 1080 | } 1081 | 1082 | #[test] 1083 | fn args_with_field_filter_fn() { 1084 | #[derive(Default)] 1085 | struct TestArgs { 1086 | m: i32, 1087 | } 1088 | #[derive(Arbitrary, Debug, PartialEq)] 1089 | #[arbitrary(args = TestArgs)] 1090 | struct TestStruct { 1091 | #[filter(is_larger_than(args.m))] 1092 | x: i32, 1093 | } 1094 | 1095 | fn is_larger_than(t: i32) -> impl Fn(&i32) -> bool { 1096 | move |x: &i32| *x > t 1097 | } 1098 | } 1099 | 1100 | #[test] 1101 | fn args_with_field_filter_fn_x2() { 1102 | #[derive(Default)] 1103 | struct TestArgs { 1104 | m: i32, 1105 | } 1106 | #[derive(Arbitrary, Debug, PartialEq)] 1107 | #[arbitrary(args = TestArgs)] 1108 | struct TestStruct { 1109 | #[filter(is_larger_than(args.m))] 1110 | #[filter(is_larger_than(args.m + 1))] 1111 | x: i32, 1112 | } 1113 | 1114 | fn is_larger_than(t: i32) -> impl Fn(&i32) -> bool { 1115 | move |x: &i32| *x > t 1116 | } 1117 | } 1118 | 1119 | #[test] 1120 | fn args_map() { 1121 | #[derive(Default)] 1122 | struct TestArgs { 1123 | m: i32, 1124 | } 1125 | #[derive(Arbitrary, Debug, PartialEq)] 1126 | #[arbitrary(args = TestArgs)] 1127 | struct TestStruct { 1128 | #[map(|x: i32| x + args.m)] 1129 | x: i32, 1130 | } 1131 | } 1132 | #[test] 1133 | fn args_map_x2() { 1134 | #[derive(Default)] 1135 | struct TestArgs { 1136 | m: i32, 1137 | } 1138 | #[derive(Arbitrary, Debug, PartialEq)] 1139 | #[arbitrary(args = TestArgs)] 1140 | struct TestStruct { 1141 | #[map(|x: i32| x + args.m)] 1142 | x: i32, 1143 | 1144 | #[map(|y: i32| y + args.m)] 1145 | y: i32, 1146 | } 1147 | } 1148 | 1149 | #[test] 1150 | fn args_map_filter_val() { 1151 | #[derive(Default)] 1152 | struct TestArgs { 1153 | m: i32, 1154 | } 1155 | #[derive(Arbitrary, Debug, PartialEq)] 1156 | #[arbitrary(args = TestArgs)] 1157 | struct TestStruct { 1158 | #[map(|x: i32| x + args.m)] 1159 | #[filter(#x > args.m)] 1160 | x: i32, 1161 | } 1162 | } 1163 | 1164 | #[test] 1165 | fn args_map_filter_val_x2() { 1166 | #[derive(Default)] 1167 | struct TestArgs { 1168 | m: i32, 1169 | } 1170 | #[derive(Arbitrary, Debug, PartialEq)] 1171 | #[arbitrary(args = TestArgs)] 1172 | struct TestStruct { 1173 | #[map(|x: i32| x + args.m)] 1174 | #[filter(#x > args.m)] 1175 | #[filter(#x > args.m + 1)] 1176 | x: i32, 1177 | } 1178 | } 1179 | 1180 | #[test] 1181 | fn args_map_filter_fn_val() { 1182 | #[derive(Default)] 1183 | struct TestArgs { 1184 | m: i32, 1185 | } 1186 | #[derive(Arbitrary, Debug, PartialEq)] 1187 | #[arbitrary(args = TestArgs)] 1188 | struct TestStruct { 1189 | #[map(|x: i32| x + args.m)] 1190 | #[filter(|&x: &i32| x > args.m)] 1191 | x: i32, 1192 | } 1193 | } 1194 | 1195 | #[test] 1196 | fn args_map_filter_fn_val_x2() { 1197 | #[derive(Default)] 1198 | struct TestArgs { 1199 | m: i32, 1200 | } 1201 | #[derive(Arbitrary, Debug, PartialEq)] 1202 | #[arbitrary(args = TestArgs)] 1203 | struct TestStruct { 1204 | #[map(|x: i32| x + args.m)] 1205 | #[filter(|&x: &i32| x > args.m)] 1206 | #[filter(|&x: &i32| x > args.m + 1)] 1207 | x: i32, 1208 | } 1209 | } 1210 | 1211 | #[test] 1212 | fn args_flat_map_x2() { 1213 | #[derive(Default)] 1214 | struct TestArgs { 1215 | m: i32, 1216 | } 1217 | #[derive(Arbitrary, Debug, PartialEq)] 1218 | #[arbitrary(args = TestArgs)] 1219 | struct TestStruct { 1220 | a0: i32, 1221 | a1: i32, 1222 | #[strategy(#a0..#a1 + args.m)] 1223 | b0: i32, 1224 | #[strategy(#a0..#a1 + args.m)] 1225 | b1: i32, 1226 | } 1227 | } 1228 | 1229 | #[test] 1230 | fn auto_bound_tuple_struct() { 1231 | #[derive(Arbitrary, Debug, PartialEq)] 1232 | struct TestStruct(T); 1233 | 1234 | assert_arbitrary(any::().prop_map(TestStruct)); 1235 | assert_arbitrary(any::().prop_map(TestStruct)); 1236 | } 1237 | 1238 | #[test] 1239 | fn auto_bound_tuple_enum() { 1240 | #[derive(Arbitrary, Debug, PartialEq)] 1241 | enum TestEnum { 1242 | A(T), 1243 | } 1244 | 1245 | assert_arbitrary(any::().prop_map(TestEnum::A)); 1246 | assert_arbitrary(any::().prop_map(TestEnum::A)); 1247 | } 1248 | 1249 | #[test] 1250 | fn auto_bound_any_attribute() { 1251 | #[derive(Arbitrary, Debug, PartialEq)] 1252 | struct TestStruct(#[any(std::default::Default::default())] T); 1253 | 1254 | assert_arbitrary(any::().prop_map(TestStruct)); 1255 | assert_arbitrary(any::().prop_map(TestStruct)); 1256 | } 1257 | 1258 | #[test] 1259 | fn auto_bound_x2() { 1260 | #[derive(Arbitrary, Debug, PartialEq)] 1261 | struct TestStruct(T1, T2); 1262 | 1263 | assert_arbitrary((any::(), any::()).prop_map(|(x, y)| TestStruct(x, y))); 1264 | } 1265 | 1266 | #[test] 1267 | fn auto_bound_map_input() { 1268 | #[derive(Debug, PartialEq, Clone)] 1269 | struct Wrapped(T); 1270 | 1271 | #[derive(Arbitrary, Debug, PartialEq)] 1272 | #[arbitrary(bound(T: Clone, ..))] 1273 | struct TestStruct { 1274 | #[by_ref] 1275 | #[map(|t: T| Wrapped(t))] 1276 | t: Wrapped, 1277 | } 1278 | } 1279 | 1280 | #[test] 1281 | fn manual_bound_type() { 1282 | #[derive(Arbitrary, Debug, PartialEq)] 1283 | #[arbitrary(bound(T))] 1284 | struct TestStruct { 1285 | #[strategy(any::())] 1286 | x: T, 1287 | } 1288 | 1289 | assert_arbitrary(any::().prop_map(|x| TestStruct { x })); 1290 | assert_arbitrary(any::().prop_map(|x| TestStruct { x })); 1291 | } 1292 | #[test] 1293 | fn manual_bound_type_x2() { 1294 | #[derive(Arbitrary, Debug, PartialEq)] 1295 | #[arbitrary(bound(T1, T2))] 1296 | struct TestStruct { 1297 | #[strategy(any::())] 1298 | x: T1, 1299 | 1300 | #[strategy(any::())] 1301 | y: T2, 1302 | } 1303 | 1304 | assert_arbitrary((any::(), any::()).prop_map(|(x, y)| TestStruct { x, y })); 1305 | } 1306 | 1307 | #[test] 1308 | fn manual_bound_type_with_auto_bound() { 1309 | #[derive(Arbitrary, Debug, PartialEq)] 1310 | #[arbitrary(bound(T1, ..))] 1311 | struct TestStruct { 1312 | #[strategy(any::())] 1313 | x: T1, 1314 | y: T2, 1315 | } 1316 | 1317 | assert_arbitrary((any::(), any::()).prop_map(|(x, y)| TestStruct { x, y })); 1318 | } 1319 | 1320 | #[test] 1321 | fn manual_bound_predicate() { 1322 | #[derive(Arbitrary, Debug, PartialEq)] 1323 | #[arbitrary(bound(T : Arbitrary + 'static))] 1324 | struct TestStruct { 1325 | #[strategy(any::())] 1326 | x: T, 1327 | } 1328 | 1329 | assert_arbitrary(any::().prop_map(|x| TestStruct { x })); 1330 | assert_arbitrary(any::().prop_map(|x| TestStruct { x })); 1331 | } 1332 | 1333 | #[test] 1334 | fn manual_bound_predicate_x2() { 1335 | #[derive(Arbitrary, Debug, PartialEq)] 1336 | #[arbitrary(bound(T1 : Arbitrary + 'static, T2 : Arbitrary + 'static))] 1337 | struct TestStruct { 1338 | #[strategy(any::())] 1339 | x: T1, 1340 | 1341 | #[strategy(any::())] 1342 | y: T2, 1343 | } 1344 | 1345 | assert_arbitrary((any::(), any::()).prop_map(|(x, y)| TestStruct { x, y })); 1346 | } 1347 | 1348 | #[test] 1349 | fn manual_bound_field() { 1350 | #[derive(Arbitrary, Debug, PartialEq)] 1351 | #[arbitrary(bound(T : Copy + Arbitrary + 'static))] 1352 | struct Inner(T); 1353 | 1354 | #[derive(Arbitrary, Debug, PartialEq)] 1355 | pub struct Outer { 1356 | #[arbitrary(bound(T : Debug + Copy + Arbitrary + 'static))] 1357 | x: Inner, 1358 | } 1359 | assert_arbitrary(any::().prop_map(|x| Outer { x: Inner(x) })); 1360 | } 1361 | 1362 | #[test] 1363 | fn manual_bound_variant() { 1364 | #[derive(Arbitrary, Debug, PartialEq)] 1365 | #[arbitrary(bound(T : Copy + Arbitrary + 'static))] 1366 | pub struct Inner(T); 1367 | 1368 | #[derive(Arbitrary, Debug, PartialEq)] 1369 | pub enum Outer { 1370 | #[arbitrary(bound(T : Debug + Copy + Arbitrary + 'static))] 1371 | X(Inner), 1372 | } 1373 | assert_arbitrary(any::().prop_map(|x| Outer::X(Inner(x)))); 1374 | } 1375 | 1376 | #[test] 1377 | fn enum_with_variant_named_parameters() { 1378 | #[deny(warnings)] 1379 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1380 | enum MyEnum { 1381 | Parameters, 1382 | SomethingElse, 1383 | } 1384 | } 1385 | 1386 | #[test] 1387 | fn map() { 1388 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1389 | struct X { 1390 | #[map(|x: u32| x + 1)] 1391 | x: u32, 1392 | } 1393 | 1394 | assert_arbitrary(any::().prop_map(|x| X { x: x + 1 })); 1395 | } 1396 | 1397 | #[test] 1398 | fn map_dependency() { 1399 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1400 | struct X { 1401 | a: u32, 1402 | #[map(|x: u32| x ^ #a)] 1403 | b: u32, 1404 | } 1405 | 1406 | assert_arbitrary((any::(), any::()).prop_map(|x| X { 1407 | a: x.0, 1408 | b: x.0 ^ x.1, 1409 | })); 1410 | } 1411 | 1412 | #[test] 1413 | fn map_dependency_by_ref() { 1414 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1415 | struct X { 1416 | #[by_ref] 1417 | a: u32, 1418 | #[map(|x: u32| x ^ *#a)] 1419 | b: u32, 1420 | } 1421 | 1422 | assert_arbitrary((any::(), any::()).prop_map(|x| X { 1423 | a: x.0, 1424 | b: x.0 ^ x.1, 1425 | })); 1426 | } 1427 | 1428 | #[test] 1429 | fn map_filter() { 1430 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1431 | struct X { 1432 | #[map(|x: u32| x + 1)] 1433 | #[filter(#x % 2 == 0)] 1434 | x: u32, 1435 | } 1436 | 1437 | assert_arbitrary( 1438 | any::() 1439 | .prop_map(|x| x + 1) 1440 | .prop_filter("", |x| x % 2 == 0) 1441 | .prop_map(|x| X { x }), 1442 | ); 1443 | } 1444 | 1445 | #[test] 1446 | fn map_strategy() { 1447 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1448 | struct X { 1449 | #[strategy(1..10u32)] 1450 | #[map(|x: u32| x * 2)] 1451 | x: u32, 1452 | } 1453 | 1454 | assert_arbitrary((1..10u32).prop_map(|x| X { x: x * 2 })); 1455 | } 1456 | 1457 | #[test] 1458 | fn map_strategy_infer() { 1459 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1460 | struct X { 1461 | #[strategy(1..10u32)] 1462 | #[map(|x| x * 2)] 1463 | x: u32, 1464 | } 1465 | 1466 | assert_arbitrary((1..10u32).prop_map(|x| X { x: x * 2 })); 1467 | } 1468 | 1469 | #[test] 1470 | fn map_strategy_any() { 1471 | fn x2(x: u32) -> u32 { 1472 | x * 2 1473 | } 1474 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1475 | struct X { 1476 | #[map(x2)] 1477 | x: u32, 1478 | } 1479 | assert_arbitrary((any::()).prop_map(|x| X { x: x * 2 })); 1480 | } 1481 | 1482 | #[test] 1483 | fn map_strategy_any_with() { 1484 | fn x2(x: A) -> u32 { 1485 | x.0 * 2 1486 | } 1487 | 1488 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1489 | #[arbitrary(args = u32)] 1490 | struct A(#[strategy(0..*args)] u32); 1491 | 1492 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1493 | struct X { 1494 | #[any(10)] 1495 | #[map(x2)] 1496 | x: u32, 1497 | } 1498 | assert_arbitrary((0..10u32).prop_map(|x| X { x: x * 2 })); 1499 | } 1500 | 1501 | #[test] 1502 | fn map_strategy_any_with_setter() { 1503 | fn x2(x: A) -> u32 { 1504 | x.0 * 2 1505 | } 1506 | 1507 | #[derive(Default)] 1508 | struct Arg { 1509 | val: u32, 1510 | } 1511 | 1512 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1513 | #[arbitrary(args = Arg)] 1514 | struct A(#[strategy(0..args.val)] u32); 1515 | 1516 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1517 | struct X { 1518 | #[any(val = 10)] 1519 | #[map(x2)] 1520 | x: u32, 1521 | } 1522 | assert_arbitrary((0..10u32).prop_map(|x| X { x: x * 2 })); 1523 | } 1524 | 1525 | #[test] 1526 | fn map_strategy_any_dependency() { 1527 | fn xor(arg: u32) -> impl Fn(u32) -> u32 { 1528 | move |x| x ^ arg 1529 | } 1530 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1531 | struct X { 1532 | a: u32, 1533 | #[map(xor(#a))] 1534 | b: u32, 1535 | } 1536 | assert_arbitrary((any::(), any::()).prop_map(|x| X { 1537 | a: x.0, 1538 | b: x.0 ^ x.1, 1539 | })); 1540 | } 1541 | 1542 | #[test] 1543 | fn map_strategy_any_dependency_by_ref() { 1544 | fn xor(arg: u32) -> impl Fn(u32) -> u32 { 1545 | move |x| x ^ arg 1546 | } 1547 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1548 | struct X { 1549 | #[by_ref] 1550 | a: u32, 1551 | #[map(xor(*#a))] 1552 | b: u32, 1553 | } 1554 | assert_arbitrary((any::(), any::()).prop_map(|x| X { 1555 | a: x.0, 1556 | b: x.0 ^ x.1, 1557 | })); 1558 | } 1559 | 1560 | #[test] 1561 | fn map_with_by_ref() { 1562 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1563 | struct X { 1564 | #[by_ref] 1565 | #[map(|x: u32| x + 1)] 1566 | x: u32, 1567 | } 1568 | 1569 | assert_arbitrary(any::().prop_map(|x| X { x: x + 1 })); 1570 | } 1571 | 1572 | #[test] 1573 | fn map_with_by_ref_and_dependency() { 1574 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1575 | struct X { 1576 | x: u32, 1577 | 1578 | #[by_ref] 1579 | #[map(|y: u32| #x ^ y)] 1580 | y: u32, 1581 | } 1582 | assert_arbitrary((any::(), any::()).prop_map(|(x, y)| X { x, y: x ^ y })); 1583 | } 1584 | 1585 | #[test] 1586 | fn map_7_field() { 1587 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1588 | struct X { 1589 | #[map(|x: u32| x + 1)] 1590 | a1: u32, 1591 | 1592 | #[map(|x: u32| x + 2)] 1593 | a2: u32, 1594 | 1595 | #[map(|x: u32| x + 3)] 1596 | a3: u32, 1597 | 1598 | #[map(|x: u32| x + 4)] 1599 | a4: u32, 1600 | 1601 | #[map(|x: u32| x + 5)] 1602 | a5: u32, 1603 | 1604 | #[map(|x: u32| x + 6)] 1605 | a6: u32, 1606 | 1607 | #[map(|x: u32| x + 7)] 1608 | a7: u32, 1609 | } 1610 | } 1611 | 1612 | #[test] 1613 | fn depend_on_map() { 1614 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1615 | struct X { 1616 | #[strategy(0..5u32)] 1617 | #[map(|x: u32| x + 5)] 1618 | a: u32, 1619 | 1620 | #[strategy(0..#a)] 1621 | b: u32, 1622 | } 1623 | 1624 | assert_arbitrary( 1625 | (0..5u32) 1626 | .prop_flat_map(|x| (Just(x + 5), 0..x + 5)) 1627 | .prop_map(|x| X { a: x.0, b: x.1 }), 1628 | ); 1629 | } 1630 | 1631 | #[test] 1632 | fn index() { 1633 | use proptest::sample::Index; 1634 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1635 | struct X { 1636 | #[strategy(1..20usize)] 1637 | a: usize, 1638 | #[map(|i: Index| i.index(#a))] 1639 | b: usize, 1640 | } 1641 | 1642 | assert_arbitrary((1..20usize, any::()).prop_map(|x| X { 1643 | a: x.0, 1644 | b: x.1.index(x.0), 1645 | })); 1646 | } 1647 | 1648 | #[test] 1649 | fn filter_with_strategy_and_map_0() { 1650 | use proptest::sample::Index; 1651 | 1652 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1653 | struct X { 1654 | #[strategy(10..100usize)] 1655 | a: usize, 1656 | 1657 | #[strategy(0..#a)] 1658 | x: usize, 1659 | 1660 | #[map(|i: Index|i.index(#a))] 1661 | #[filter(#y % 2 == 0)] 1662 | y: usize, 1663 | } 1664 | 1665 | let s = (10..100usize, any::()) 1666 | .prop_flat_map(|x| (0..x.0, Just(x.1.index(x.0)), Just(x.0), Just(x.1))) 1667 | .prop_map(|x| X { 1668 | a: x.2, 1669 | x: x.0, 1670 | y: x.1, 1671 | }) 1672 | .prop_filter("", |x| x.y % 2 == 0); 1673 | 1674 | assert_arbitrary(s); 1675 | } 1676 | 1677 | #[test] 1678 | fn filter_with_strategy_and_map_1() { 1679 | use proptest::sample::Index; 1680 | 1681 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 1682 | struct X { 1683 | #[strategy(10..100usize)] 1684 | a: usize, 1685 | 1686 | #[strategy(0..#a)] 1687 | x: usize, 1688 | 1689 | #[map(|i: Index|i.index(#a))] 1690 | #[filter(#y % 2 == 1)] 1691 | y: usize, 1692 | } 1693 | 1694 | let s = (10..100usize, any::()) 1695 | .prop_flat_map(|x| (0..x.0, Just(x.1.index(x.0)), Just(x.0), Just(x.1))) 1696 | .prop_map(|x| X { 1697 | a: x.2, 1698 | x: x.0, 1699 | y: x.1, 1700 | }) 1701 | .prop_filter("", |x| x.y % 2 == 1); 1702 | 1703 | assert_arbitrary(s); 1704 | } 1705 | -------------------------------------------------------------------------------- /tests/compile_fail.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | #[ignore] 3 | fn compile_fail() { 4 | trybuild::TestCases::new().compile_fail("tests/compile_fail/*.rs") 5 | } 6 | -------------------------------------------------------------------------------- /tests/compile_fail/args_not_impl_default.rs: -------------------------------------------------------------------------------- 1 | use test_strategy::Arbitrary; 2 | struct TestArgs { 3 | x_max: u32, 4 | } 5 | #[derive(Arbitrary, Debug, PartialEq)] 6 | #[arbitrary(args = TestArgs)] 7 | struct TestStruct { 8 | #[strategy(0..args.x_max)] 9 | x: u32, 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /tests/compile_fail/args_not_impl_default.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `TestArgs: Default` is not satisfied 2 | --> tests/compile_fail/args_not_impl_default.rs:6:20 3 | | 4 | 6 | #[arbitrary(args = TestArgs)] 5 | | ^^^^^^^^ the trait `Default` is not implemented for `TestArgs` 6 | | 7 | note: required by a bound in `proptest::arbitrary::Arbitrary::Parameters` 8 | --> $CARGO/proptest-1.7.0/src/arbitrary/traits.rs 9 | | 10 | | type Parameters: Default; 11 | | ^^^^^^^ required by this bound in `Arbitrary::Parameters` 12 | help: consider annotating `TestArgs` with `#[derive(Default)]` 13 | | 14 | 2 + #[derive(Default)] 15 | 3 | struct TestArgs { 16 | | 17 | -------------------------------------------------------------------------------- /tests/compile_fail/cyclic_dependency.rs: -------------------------------------------------------------------------------- 1 | use test_strategy::Arbitrary; 2 | #[derive(Arbitrary, Debug)] 3 | struct TestStruct { 4 | #[strategy(1..#y)] 5 | x: u32, 6 | 7 | #[strategy(1..#x)] 8 | y: u32, 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /tests/compile_fail/cyclic_dependency.stderr: -------------------------------------------------------------------------------- 1 | error: found cyclic dependency. (x -> y -> x) 2 | --> $DIR/cyclic_dependency.rs:2:10 3 | | 4 | 2 | #[derive(Arbitrary, Debug)] 5 | | ^^^^^^^^^ 6 | | 7 | = note: this error originates in the derive macro `Arbitrary` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /tests/compile_fail/filter_enum_sharp_val.rs: -------------------------------------------------------------------------------- 1 | use test_strategy::Arbitrary; 2 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 3 | #[filter(#x > #y)] 4 | enum TestEnum { 5 | A { x: i32, y: i32 }, 6 | B(i32), 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /tests/compile_fail/filter_enum_sharp_val.stderr: -------------------------------------------------------------------------------- 1 | error: cannot use `#x` in this position. 2 | --> $DIR/filter_enum_sharp_val.rs:3:11 3 | | 4 | 3 | #[filter(#x > #y)] 5 | | ^ 6 | -------------------------------------------------------------------------------- /tests/compile_fail/group_span.rs: -------------------------------------------------------------------------------- 1 | use test_strategy::Arbitrary; 2 | 3 | // A compile error needs to occur on line 6, not on line 4. 4 | #[derive(Debug, Arbitrary)] 5 | struct X { 6 | #[strategy((0u8..10u8))] 7 | x: u32, 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /tests/compile_fail/group_span.stderr: -------------------------------------------------------------------------------- 1 | warning: unnecessary parentheses around block return value 2 | --> tests/compile_fail/group_span.rs:6:16 3 | | 4 | 6 | #[strategy((0u8..10u8))] 5 | | ^ ^ 6 | | 7 | = note: `#[warn(unused_parens)]` on by default 8 | help: remove these parentheses 9 | | 10 | 6 - #[strategy((0u8..10u8))] 11 | 6 + #[strategy(0u8..10u8)] 12 | | 13 | 14 | error[E0271]: type mismatch resolving ` as Strategy>::Value == u32` 15 | --> tests/compile_fail/group_span.rs:6:16 16 | | 17 | 6 | #[strategy((0u8..10u8))] 18 | | ^^^^^^^^^^^ expected `u32`, found `u8` 19 | | 20 | note: required by a bound in `_strategy_of_x` 21 | --> tests/compile_fail/group_span.rs:7:8 22 | | 23 | 6 | #[strategy((0u8..10u8))] 24 | | ----------- required by a bound in this function 25 | 7 | x: u32, 26 | | ^^^ required by this bound in `_strategy_of_x` 27 | 28 | error[E0271]: type mismatch resolving ` as Strategy>::Value == u32` 29 | --> tests/compile_fail/group_span.rs:4:17 30 | | 31 | 4 | #[derive(Debug, Arbitrary)] 32 | | ^^^^^^^^^ expected `u32`, found `u8` 33 | | 34 | note: required by a bound in `_strategy_of_x` 35 | --> tests/compile_fail/group_span.rs:7:8 36 | | 37 | 6 | #[strategy((0u8..10u8))] 38 | | ----------- required by a bound in this function 39 | 7 | x: u32, 40 | | ^^^ required by this bound in `_strategy_of_x` 41 | = note: this error originates in the derive macro `Arbitrary` (in Nightly builds, run with -Z macro-backtrace for more info) 42 | -------------------------------------------------------------------------------- /tests/compile_fail/invalid_filter_field.rs: -------------------------------------------------------------------------------- 1 | use test_strategy::Arbitrary; 2 | #[derive(Arbitrary, Debug, PartialEq)] 3 | struct TestStruct { 4 | #[strategy(1..10u32)] 5 | #[filter(bad_filter)] 6 | x: u32, 7 | } 8 | fn bad_filter() {} 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /tests/compile_fail/invalid_filter_field.stderr: -------------------------------------------------------------------------------- 1 | error[E0593]: function is expected to take 1 argument, but it takes 0 arguments 2 | --> tests/compile_fail/invalid_filter_field.rs:5:14 3 | | 4 | 5 | #[filter(bad_filter)] 5 | | ^^^^^^^^^^ expected function that takes 1 argument 6 | ... 7 | 8 | fn bad_filter() {} 8 | | --------------- takes 0 arguments 9 | | 10 | note: required by a bound in `_filter_fn` 11 | --> tests/compile_fail/invalid_filter_field.rs:2:10 12 | | 13 | 2 | #[derive(Arbitrary, Debug, PartialEq)] 14 | | ^^^^^^^^^ required by this bound in `_filter_fn` 15 | = note: this error originates in the derive macro `Arbitrary` (in Nightly builds, run with -Z macro-backtrace for more info) 16 | 17 | error[E0593]: function is expected to take 1 argument, but it takes 0 arguments 18 | --> tests/compile_fail/invalid_filter_field.rs:2:10 19 | | 20 | 2 | #[derive(Arbitrary, Debug, PartialEq)] 21 | | ^^^^^^^^^ expected function that takes 1 argument 22 | ... 23 | 8 | fn bad_filter() {} 24 | | --------------- takes 0 arguments 25 | | 26 | note: required by a bound in `_filter_fn` 27 | --> tests/compile_fail/invalid_filter_field.rs:2:10 28 | | 29 | 2 | #[derive(Arbitrary, Debug, PartialEq)] 30 | | ^^^^^^^^^ required by this bound in `_filter_fn` 31 | = note: this error originates in the derive macro `Arbitrary` (in Nightly builds, run with -Z macro-backtrace for more info) 32 | -------------------------------------------------------------------------------- /tests/compile_fail/invalid_filter_field_expr.rs: -------------------------------------------------------------------------------- 1 | use test_strategy::Arbitrary; 2 | #[derive(Arbitrary, Debug, PartialEq)] 3 | struct TestStruct { 4 | #[filter(#x)] 5 | x: u32, 6 | } 7 | fn bad_filter() {} 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /tests/compile_fail/invalid_filter_field_expr.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> tests/compile_fail/invalid_filter_field_expr.rs:4:15 3 | | 4 | 4 | #[filter(#x)] 5 | | ^ expected `bool`, found `u32` 6 | -------------------------------------------------------------------------------- /tests/compile_fail/invalid_strategy.rs: -------------------------------------------------------------------------------- 1 | use test_strategy::Arbitrary; 2 | #[derive(Arbitrary, Debug)] 3 | struct TestStruct { 4 | #[strategy(0..10usize)] 5 | x: u8, 6 | } 7 | 8 | fn main() {} 9 | -------------------------------------------------------------------------------- /tests/compile_fail/invalid_strategy.stderr: -------------------------------------------------------------------------------- 1 | error[E0271]: type mismatch resolving ` as Strategy>::Value == u8` 2 | --> tests/compile_fail/invalid_strategy.rs:4:16 3 | | 4 | 4 | #[strategy(0..10usize)] 5 | | ^--------- 6 | | | 7 | | expected `u8`, found `usize` 8 | | this tail expression is of type `Range` 9 | | 10 | note: required by a bound in `_strategy_of_x` 11 | --> tests/compile_fail/invalid_strategy.rs:5:8 12 | | 13 | 4 | #[strategy(0..10usize)] 14 | | - required by a bound in this function 15 | 5 | x: u8, 16 | | ^^ required by this bound in `_strategy_of_x` 17 | 18 | error[E0271]: type mismatch resolving ` as Strategy>::Value == u8` 19 | --> tests/compile_fail/invalid_strategy.rs:2:10 20 | | 21 | 2 | #[derive(Arbitrary, Debug)] 22 | | ^^^^^^^^^ expected `u8`, found `usize` 23 | | 24 | note: required by a bound in `_strategy_of_x` 25 | --> tests/compile_fail/invalid_strategy.rs:5:8 26 | | 27 | 4 | #[strategy(0..10usize)] 28 | | - required by a bound in this function 29 | 5 | x: u8, 30 | | ^^ required by this bound in `_strategy_of_x` 31 | = note: this error originates in the derive macro `Arbitrary` (in Nightly builds, run with -Z macro-backtrace for more info) 32 | -------------------------------------------------------------------------------- /tests/compile_fail/invalid_weight.rs: -------------------------------------------------------------------------------- 1 | use test_strategy::Arbitrary; 2 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 3 | enum TestEnum { 4 | #[weight(1.1)] 5 | X, 6 | #[weight(2)] 7 | Y, 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /tests/compile_fail/invalid_weight.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> tests/compile_fail/invalid_weight.rs:4:14 3 | | 4 | 4 | #[weight(1.1)] 5 | | ^^^ 6 | | | 7 | | expected `u32`, found floating-point number 8 | | arguments to this function are incorrect 9 | | 10 | note: function defined here 11 | --> tests/compile_fail/invalid_weight.rs:2:10 12 | | 13 | 2 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 14 | | ^^^^^^^^^ 15 | = note: this error originates in the derive macro `Arbitrary` (in Nightly builds, run with -Z macro-backtrace for more info) 16 | -------------------------------------------------------------------------------- /tests/compile_fail/self_dependency.rs: -------------------------------------------------------------------------------- 1 | use test_strategy::Arbitrary; 2 | #[derive(Arbitrary, Debug)] 3 | struct TestStruct { 4 | #[strategy(1..#x)] 5 | x: u32, 6 | } 7 | 8 | fn main() {} 9 | -------------------------------------------------------------------------------- /tests/compile_fail/self_dependency.stderr: -------------------------------------------------------------------------------- 1 | error: found cyclic dependency. (x -> x) 2 | --> $DIR/self_dependency.rs:2:10 3 | | 4 | 2 | #[derive(Arbitrary, Debug)] 5 | | ^^^^^^^^^ 6 | | 7 | = note: this error originates in the derive macro `Arbitrary` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /tests/compile_fail/sharp_val_not_found_field.rs: -------------------------------------------------------------------------------- 1 | use test_strategy::Arbitrary; 2 | #[derive(Arbitrary, Debug, PartialEq)] 3 | struct TestStruct { 4 | #[strategy(0..#y)] 5 | x: u32, 6 | } 7 | 8 | fn main() {} 9 | -------------------------------------------------------------------------------- /tests/compile_fail/sharp_val_not_found_field.stderr: -------------------------------------------------------------------------------- 1 | error: cannot find value `#y` in this scope. 2 | --> $DIR/sharp_val_not_found_field.rs:4:20 3 | | 4 | 4 | #[strategy(0..#y)] 5 | | ^ 6 | -------------------------------------------------------------------------------- /tests/compile_fail/sharp_val_span.rs: -------------------------------------------------------------------------------- 1 | use test_strategy::Arbitrary; 2 | 3 | #[derive(Arbitrary, Debug)] 4 | struct TestInput { 5 | #[strategy(1..10u32)] 6 | x: u32, 7 | 8 | #[strategy(0..*#x)] 9 | y: u32, 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /tests/compile_fail/sharp_val_span.stderr: -------------------------------------------------------------------------------- 1 | error[E0614]: type `u32` cannot be dereferenced 2 | --> tests/compile_fail/sharp_val_span.rs:8:19 3 | | 4 | 8 | #[strategy(0..*#x)] 5 | | ^^^ can't be dereferenced 6 | -------------------------------------------------------------------------------- /tests/compile_fail/variant_any_attr.rs: -------------------------------------------------------------------------------- 1 | use test_strategy::Arbitrary; 2 | #[derive(Arbitrary, Debug)] 3 | enum X { 4 | #[any] 5 | A, 6 | } 7 | 8 | fn main() {} 9 | -------------------------------------------------------------------------------- /tests/compile_fail/variant_any_attr.stderr: -------------------------------------------------------------------------------- 1 | error: `#[any]` cannot be specified for a variant. Consider specifying it for a field instead. 2 | --> tests/compile_fail/variant_any_attr.rs:4:5 3 | | 4 | 4 | #[any] 5 | | ^ 6 | -------------------------------------------------------------------------------- /tests/compile_fail/variant_filter_has_no_fields.rs: -------------------------------------------------------------------------------- 1 | use test_strategy::Arbitrary; 2 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 3 | enum TestEnum { 4 | #[filter(Self::is_match)] 5 | A { 6 | x: i32, 7 | y: i32, 8 | }, 9 | B(i32), 10 | } 11 | 12 | impl TestEnum { 13 | fn is_match(self) -> bool { 14 | true 15 | } 16 | } 17 | 18 | fn main() {} 19 | -------------------------------------------------------------------------------- /tests/compile_fail/variant_filter_has_no_fields.stderr: -------------------------------------------------------------------------------- 1 | error: Filters that reference `self` in the variant (filters with no reference to the field) cannot be set. 2 | --> tests/compile_fail/variant_filter_has_no_fields.rs:4:13 3 | | 4 | 4 | #[filter(Self::is_match)] 5 | | ^^^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/compile_fail/variant_strategy_attr.rs: -------------------------------------------------------------------------------- 1 | use test_strategy::Arbitrary; 2 | #[derive(Arbitrary, Debug)] 3 | enum X { 4 | #[strategy(0..10usize)] 5 | A(usize), 6 | } 7 | 8 | fn main() {} 9 | -------------------------------------------------------------------------------- /tests/compile_fail/variant_strategy_attr.stderr: -------------------------------------------------------------------------------- 1 | error: `#[strategy]` cannot be specified for a variant. Consider specifying it for a field instead. 2 | --> tests/compile_fail/variant_strategy_attr.rs:4:5 3 | | 4 | 4 | #[strategy(0..10usize)] 5 | | ^ 6 | -------------------------------------------------------------------------------- /tests/compile_fail/weight_attr_more_arg.rs: -------------------------------------------------------------------------------- 1 | use test_strategy::Arbitrary; 2 | #[derive(Arbitrary, Debug, PartialEq, Clone)] 3 | enum WeightAttrMoreArg { 4 | #[weight(1, 2)] 5 | X, 6 | Y, 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /tests/compile_fail/weight_attr_more_arg.stderr: -------------------------------------------------------------------------------- 1 | error: too many arguments. 2 | --> $DIR/weight_attr_more_arg.rs:4:17 3 | | 4 | 4 | #[weight(1, 2)] 5 | | ^ 6 | -------------------------------------------------------------------------------- /tests/proptest_fn.rs: -------------------------------------------------------------------------------- 1 | use ::std::result::Result; 2 | use std::{future::Future, rc::Rc}; 3 | 4 | use ::proptest::test_runner::TestCaseError; 5 | use proptest::{prelude::ProptestConfig, prop_assert}; 6 | use test_strategy::proptest; 7 | use tokio::task::yield_now; 8 | 9 | use googletest::expect_that; 10 | use googletest::gtest; 11 | use googletest::matchers::*; 12 | use googletest::verify_that; 13 | 14 | #[proptest] 15 | fn example(_x: u32, #[strategy(1..10u32)] y: u32, #[strategy(0..#y)] z: u32) { 16 | assert!(1 <= y); 17 | assert!(y < 10); 18 | assert!(z <= y); 19 | } 20 | 21 | #[proptest] 22 | fn param_x1(_x: u32) {} 23 | 24 | #[proptest] 25 | fn param_x2(_x: u32, _y: u32) {} 26 | 27 | #[proptest] 28 | fn param_mut(mut _x: u32) { 29 | _x = 0; 30 | } 31 | #[proptest] 32 | fn param_mut_x2(mut _x: u32, mut _y: u32) { 33 | _x = 0; 34 | _y = 0; 35 | } 36 | 37 | #[proptest] 38 | fn with_strategy(#[strategy(2..10u32)] x: u32) { 39 | assert!(2 <= x); 40 | assert!(x < 10); 41 | } 42 | 43 | #[proptest] 44 | fn with_map( 45 | #[strategy(2..10u32)] 46 | #[map(|x| x + 2)] 47 | x: u32, 48 | ) { 49 | assert!(4 <= x); 50 | assert!(x < 12); 51 | } 52 | 53 | #[proptest(ProptestConfig { timeout: 3, ..ProptestConfig::default() })] 54 | #[should_panic] 55 | fn config_expr() { 56 | std::thread::sleep(std::time::Duration::from_millis(30)); 57 | } 58 | 59 | #[proptest(timeout = 3)] 60 | #[should_panic] 61 | fn config_field() { 62 | std::thread::sleep(std::time::Duration::from_millis(30)); 63 | } 64 | 65 | #[proptest(ProptestConfig::default(), timeout = 3)] 66 | #[should_panic] 67 | fn config_expr_and_field() { 68 | std::thread::sleep(std::time::Duration::from_millis(30)); 69 | } 70 | 71 | #[proptest(async = "tokio")] 72 | async fn tokio_test() { 73 | tokio::task::spawn(async {}).await.unwrap() 74 | } 75 | 76 | #[proptest(async = "tokio")] 77 | async fn tokio_test_no_copy_arg(#[strategy("a+")] s: String) { 78 | prop_assert!(s.contains('a')); 79 | } 80 | 81 | #[proptest(async = "tokio")] 82 | async fn tokio_test_prop_assert() { 83 | prop_assert!(true); 84 | } 85 | 86 | #[should_panic] 87 | #[proptest(async = "tokio")] 88 | async fn tokio_test_prop_assert_false() { 89 | prop_assert!(false); 90 | } 91 | 92 | fn tokio_ct(future: impl Future>) -> Result<(), TestCaseError> { 93 | tokio::runtime::Builder::new_current_thread() 94 | .build() 95 | .unwrap() 96 | .block_on(future) 97 | } 98 | 99 | #[proptest(async = tokio_ct)] 100 | async fn async_expr() {} 101 | 102 | #[proptest(async = tokio_ct)] 103 | async fn async_expr_non_send() { 104 | let x = Rc::new(0); 105 | yield_now().await; 106 | drop(x); 107 | } 108 | 109 | #[proptest(async = tokio_ct)] 110 | async fn async_expr_no_copy_arg(#[strategy("a+")] s: String) { 111 | prop_assert!(s.contains('a')); 112 | } 113 | 114 | #[proptest(async = tokio_ct)] 115 | async fn async_expr_test_prop_assert() { 116 | prop_assert!(true); 117 | } 118 | 119 | #[should_panic] 120 | #[proptest(async = tokio_ct)] 121 | async fn async_expr_test_prop_assert_false() { 122 | prop_assert!(false); 123 | } 124 | 125 | struct NotImplError0; 126 | struct NotImplError1; 127 | 128 | #[derive(Debug)] 129 | struct CustomError(#[allow(dead_code)] TestCaseError); 130 | 131 | impl CustomError { 132 | fn new() -> Self { 133 | CustomError(TestCaseError::fail("Custom")) 134 | } 135 | } 136 | 137 | impl std::error::Error for CustomError {} 138 | impl std::fmt::Display for CustomError { 139 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 140 | write!(f, "CustomError") 141 | } 142 | } 143 | impl From for CustomError { 144 | fn from(e: TestCaseError) -> Self { 145 | CustomError(e) 146 | } 147 | } 148 | impl From for CustomError { 149 | fn from(_: NotImplError0) -> Self { 150 | CustomError(TestCaseError::fail("Custom")) 151 | } 152 | } 153 | impl From for CustomError { 154 | fn from(_: NotImplError1) -> Self { 155 | CustomError(TestCaseError::fail("Custom")) 156 | } 157 | } 158 | 159 | macro_rules! prop_assert_custom { 160 | ($cond:expr) => { 161 | prop_assert_custom!($cond, concat!("assertion failed: ", stringify!($cond))) 162 | }; 163 | ($cond:expr, $($fmt:tt)*) => { 164 | if !$cond { 165 | let message = format!($($fmt)*); 166 | let message = format!("{} at {}:{}", message, file!(), line!()); 167 | ::core::result::Result::Err(::proptest::test_runner::TestCaseError::fail(message))?; 168 | } 169 | }; 170 | } 171 | 172 | #[proptest] 173 | fn custom_error_ok(#[strategy(1..10u8)] x: u8) -> Result<(), CustomError> { 174 | prop_assert_custom!(x < 10); 175 | not_impl_error_ok()?; 176 | Ok(()) 177 | } 178 | #[proptest(async = "tokio")] 179 | async fn custom_error_ok_async(#[strategy(1..10u8)] x: u8) -> Result<(), CustomError> { 180 | prop_assert_custom!(x < 10); 181 | not_impl_error_ok()?; 182 | yield_now().await; 183 | Ok(()) 184 | } 185 | 186 | #[should_panic] 187 | #[proptest] 188 | fn custom_error_prop_assesrt_fail(#[strategy(1..10u8)] x: u8) -> Result<(), CustomError> { 189 | prop_assert_custom!(x >= 10); 190 | Ok(()) 191 | } 192 | 193 | #[proptest] 194 | #[should_panic] 195 | fn custom_error_err(#[strategy(1..10u8)] x: u8) -> Result<(), CustomError> { 196 | prop_assert_custom!(x < 10); 197 | not_impl_error_err()?; 198 | Ok(()) 199 | } 200 | 201 | #[proptest(async = "tokio")] 202 | #[should_panic] 203 | async fn custom_error_err_async(#[strategy(1..10u8)] x: u8) -> Result<(), CustomError> { 204 | prop_assert_custom!(x < 10); 205 | not_impl_error_err()?; 206 | yield_now().await; 207 | Ok(()) 208 | } 209 | 210 | #[proptest] 211 | fn custom_error_ok_2(#[strategy(1..10u8)] x: u8) -> Result<(), CustomError> { 212 | prop_assert_custom!(x < 10); 213 | not_impl_error_ok()?; 214 | not_impl_error_ok_1()?; 215 | Ok(()) 216 | } 217 | 218 | fn not_impl_error_ok() -> Result<(), NotImplError0> { 219 | Ok(()) 220 | } 221 | fn not_impl_error_err() -> Result<(), NotImplError0> { 222 | Err(NotImplError0) 223 | } 224 | 225 | fn not_impl_error_ok_1() -> Result<(), NotImplError1> { 226 | Ok(()) 227 | } 228 | 229 | macro_rules! prop_assert_anyhow { 230 | ($cond:expr) => { 231 | prop_assert_anyhow!($cond, concat!("assertion failed: ", stringify!($cond))) 232 | }; 233 | ($cond:expr, $($fmt:tt)*) => { 234 | if !$cond { 235 | anyhow::bail!("{} at {}:{}", format!($($fmt)*), file!(), line!()); 236 | } 237 | }; 238 | } 239 | 240 | #[proptest] 241 | fn anyhow_result_ok(#[strategy(1..10u8)] x: u8) -> anyhow::Result<()> { 242 | prop_assert_anyhow!(x < 10); 243 | Ok(()) 244 | } 245 | 246 | #[proptest(async = "tokio")] 247 | async fn anyhow_result_ok_async(#[strategy(1..10u8)] x: u8) -> anyhow::Result<()> { 248 | prop_assert_anyhow!(x < 10); 249 | yield_now().await; 250 | Ok(()) 251 | } 252 | 253 | #[proptest] 254 | #[should_panic] 255 | fn anyhow_result_prop_assert_fail(#[strategy(1..10u8)] x: u8) -> anyhow::Result<()> { 256 | prop_assert_anyhow!(x >= 10); 257 | Ok(()) 258 | } 259 | 260 | #[proptest(async = "tokio")] 261 | #[should_panic] 262 | async fn anyhow_result_prop_assert_fail_async(#[strategy(1..10u8)] x: u8) -> anyhow::Result<()> { 263 | prop_assert_anyhow!(x >= 10); 264 | yield_now().await; 265 | Ok(()) 266 | } 267 | 268 | #[proptest] 269 | #[should_panic] 270 | fn anyhow_result_err(#[strategy(1..10u8)] x: u8) -> anyhow::Result<()> { 271 | prop_assert_anyhow!(x < 10); 272 | Err(CustomError::new())?; 273 | Ok(()) 274 | } 275 | 276 | #[proptest(async = "tokio")] 277 | #[should_panic] 278 | async fn anyhow_result_err_async(#[strategy(1..10u8)] x: u8) -> anyhow::Result<()> { 279 | prop_assert_anyhow!(x < 10); 280 | Err(CustomError::new())?; 281 | yield_now().await; 282 | Ok(()) 283 | } 284 | 285 | #[proptest] 286 | #[should_panic] 287 | fn anyhow_result_bail(#[strategy(1..10u8)] x: u8) -> anyhow::Result<()> { 288 | prop_assert_anyhow!(x < 10); 289 | anyhow::bail!("error"); 290 | } 291 | 292 | #[proptest(async = "tokio")] 293 | #[should_panic] 294 | async fn anyhow_result_bail_async(#[strategy(1..10u8)] x: u8) -> anyhow::Result<()> { 295 | prop_assert_anyhow!(x < 10); 296 | yield_now().await; 297 | anyhow::bail!("error"); 298 | } 299 | 300 | #[proptest] 301 | #[gtest] 302 | fn googletest_result(#[strategy(1..10u8)] x: u8) -> googletest::Result<()> { 303 | expect_that!(x, ge(1)); 304 | verify_that!(x, lt(10)) 305 | } 306 | -------------------------------------------------------------------------------- /tests/readme_arbitrary.rs: -------------------------------------------------------------------------------- 1 | mod test_helpers; 2 | use test_helpers::*; 3 | 4 | #[test] 5 | fn example_1() { 6 | use proptest::{ 7 | arbitrary::any, arbitrary::Arbitrary, strategy::BoxedStrategy, strategy::Strategy, 8 | }; 9 | use test_strategy::Arbitrary; 10 | 11 | #[derive(Arbitrary, Debug, PartialEq)] 12 | struct MyStructA { 13 | x: u32, 14 | y: u32, 15 | } 16 | #[derive(Debug, PartialEq)] 17 | struct MyStructB { 18 | x: u32, 19 | y: u32, 20 | } 21 | impl Arbitrary for MyStructB { 22 | type Parameters = (); 23 | type Strategy = BoxedStrategy; 24 | 25 | fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { 26 | let x = any::(); 27 | let y = any::(); 28 | (x, y).prop_map(|(x, y)| Self { x, y }).boxed() 29 | } 30 | } 31 | 32 | assert_arbitrary(any::().prop_map(|MyStructB { x, y }| MyStructA { x, y })); 33 | } 34 | 35 | #[test] 36 | fn any_expr() { 37 | use proptest::collection::size_range; 38 | use test_strategy::Arbitrary; 39 | 40 | #[derive(Arbitrary, Debug, PartialEq)] 41 | struct TestStructA { 42 | #[any(size_range(0..16).lift())] 43 | x: Vec, 44 | } 45 | 46 | use proptest::arbitrary::any_with; 47 | #[derive(Arbitrary, Debug, PartialEq)] 48 | struct TestStructB { 49 | #[strategy(any_with::>(size_range(0..16).lift()))] 50 | x: Vec, 51 | } 52 | 53 | use proptest::arbitrary::any; 54 | use proptest::strategy::Strategy; 55 | assert_eq_strategy( 56 | any::(), 57 | any::().prop_map(|TestStructB { x }| TestStructA { x }), 58 | ); 59 | } 60 | 61 | #[test] 62 | fn any_field() { 63 | use test_strategy::Arbitrary; 64 | 65 | #[derive(Arbitrary, Debug, PartialEq)] 66 | struct TestInputA { 67 | #[any(InnerArgs { upper : 20, ..InnerArgs::default() })] 68 | a: Inner, 69 | } 70 | #[derive(Arbitrary, Debug, PartialEq)] 71 | struct TestInputB { 72 | #[any(upper = 20)] 73 | a: Inner, 74 | } 75 | #[derive(Arbitrary, Debug, PartialEq)] 76 | struct TestInputC { 77 | #[any(InnerArgs::default(), upper = 20)] 78 | a: Inner, 79 | } 80 | 81 | #[derive(Default)] 82 | struct InnerArgs { 83 | lower: i32, 84 | upper: i32, 85 | } 86 | 87 | #[derive(Arbitrary, Debug, PartialEq)] 88 | #[arbitrary(args = InnerArgs)] 89 | struct Inner { 90 | #[strategy(args.lower..args.upper)] 91 | x: i32, 92 | } 93 | 94 | use proptest::arbitrary::any; 95 | use proptest::strategy::Strategy; 96 | assert_eq_strategy( 97 | any::(), 98 | any::().prop_map(|TestInputB { a }| TestInputA { a }), 99 | ); 100 | assert_eq_strategy( 101 | any::(), 102 | any::().prop_map(|TestInputC { a }| TestInputA { a }), 103 | ); 104 | } 105 | 106 | #[test] 107 | fn weight() { 108 | use test_strategy::Arbitrary; 109 | 110 | #[derive(Arbitrary, Debug)] 111 | enum TestInput { 112 | A, 113 | 114 | #[weight(2)] 115 | B, 116 | } 117 | } 118 | 119 | #[test] 120 | fn weight_0() { 121 | use test_strategy::Arbitrary; 122 | 123 | #[derive(Debug)] 124 | struct NotArbitrary; 125 | 126 | #[derive(Arbitrary, Debug)] 127 | enum TestInput { 128 | A, 129 | 130 | #[allow(dead_code)] 131 | #[weight(0)] 132 | B(NotArbitrary), 133 | } 134 | } 135 | 136 | #[test] 137 | fn filter_field_expr() { 138 | use test_strategy::Arbitrary; 139 | 140 | #[derive(Arbitrary, Debug)] 141 | struct TestInput { 142 | #[filter(#x % 2 == 0)] 143 | #[allow(dead_code)] 144 | x: u32, 145 | } 146 | } 147 | 148 | #[test] 149 | fn filter_struct_expr() { 150 | use test_strategy::Arbitrary; 151 | 152 | #[derive(Arbitrary, Debug)] 153 | #[filter((#x + #y) % 2 == 0)] 154 | struct TestInput { 155 | #[allow(dead_code)] 156 | x: u32, 157 | #[allow(dead_code)] 158 | y: u32, 159 | } 160 | } 161 | 162 | #[test] 163 | fn filter_struct_expr_self() { 164 | use test_strategy::Arbitrary; 165 | 166 | #[derive(Arbitrary, Debug)] 167 | #[filter((#self.x + #self.y) % 2 == 0)] 168 | struct TestInput { 169 | x: u32, 170 | y: u32, 171 | } 172 | } 173 | 174 | #[test] 175 | fn filter_field_fn() { 176 | use test_strategy::Arbitrary; 177 | 178 | #[derive(Arbitrary, Debug)] 179 | struct TestInput { 180 | #[filter(is_even)] 181 | #[allow(dead_code)] 182 | x: u32, 183 | } 184 | fn is_even(x: &u32) -> bool { 185 | x % 2 == 0 186 | } 187 | } 188 | 189 | #[test] 190 | fn filter_struct_fn() { 191 | use test_strategy::Arbitrary; 192 | 193 | #[derive(Arbitrary, Debug)] 194 | #[filter(x_is_even)] 195 | struct TestInput { 196 | x: u32, 197 | } 198 | fn x_is_even(input: &TestInput) -> bool { 199 | input.x % 2 == 0 200 | } 201 | } 202 | 203 | #[test] 204 | fn filter_name() { 205 | use test_strategy::Arbitrary; 206 | 207 | #[derive(Arbitrary, Debug)] 208 | struct TestInput { 209 | #[filter("filter name", #x % 2 == 0)] 210 | #[allow(dead_code)] 211 | x: u32, 212 | } 213 | } 214 | 215 | #[test] 216 | fn by_ref_strategy() { 217 | use test_strategy::Arbitrary; 218 | 219 | #[derive(Arbitrary, Debug)] 220 | struct TestInput { 221 | #[by_ref] 222 | #[strategy(1..10u32)] 223 | #[allow(dead_code)] 224 | x: u32, 225 | 226 | #[strategy(0..*#x)] 227 | #[allow(dead_code)] 228 | y: u32, 229 | } 230 | } 231 | 232 | #[test] 233 | fn arbitrary_args() { 234 | use test_strategy::Arbitrary; 235 | 236 | #[derive(Debug, Default)] 237 | struct TestInputArgs { 238 | x_max: u32, 239 | } 240 | 241 | #[derive(Arbitrary, Debug)] 242 | #[arbitrary(args = TestInputArgs)] 243 | struct TestInput { 244 | #[strategy(0..=args.x_max)] 245 | #[allow(dead_code)] 246 | x: u32, 247 | } 248 | } 249 | 250 | #[test] 251 | fn bound_auto() { 252 | use proptest::{ 253 | arbitrary::any, arbitrary::Arbitrary, strategy::BoxedStrategy, strategy::Strategy, 254 | }; 255 | use test_strategy::Arbitrary; 256 | 257 | #[derive(Arbitrary, Debug, PartialEq)] 258 | struct TestInputA { 259 | x: T, 260 | } 261 | 262 | #[derive(Debug, PartialEq)] 263 | struct TestInputB { 264 | x: T, 265 | } 266 | impl Arbitrary for TestInputB { 267 | type Parameters = (); 268 | type Strategy = BoxedStrategy; 269 | 270 | fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { 271 | any::().prop_map(|x| Self { x }).boxed() 272 | } 273 | } 274 | 275 | assert_arbitrary(any::>().prop_map(|TestInputB { x }| TestInputA { x })); 276 | } 277 | 278 | #[test] 279 | fn bound_both() { 280 | use proptest::arbitrary::any_with; 281 | use test_strategy::Arbitrary; 282 | 283 | #[derive(Arbitrary, Debug, PartialEq)] 284 | #[arbitrary(bound(T1, ..))] 285 | struct TestInput { 286 | #[strategy(any_with::(Default::default()))] 287 | x: T1, 288 | 289 | y: T2, 290 | } 291 | } 292 | 293 | #[test] 294 | fn bound_manual_type() { 295 | use proptest::arbitrary::any_with; 296 | use test_strategy::Arbitrary; 297 | 298 | #[derive(Arbitrary, Debug, PartialEq)] 299 | #[arbitrary(bound(T))] 300 | struct TestInput { 301 | #[strategy(any_with::(Default::default()))] 302 | x: T, 303 | } 304 | } 305 | 306 | #[test] 307 | fn bound_manual_predicate() { 308 | use proptest::arbitrary::{any_with, Arbitrary}; 309 | use test_strategy::Arbitrary; 310 | 311 | #[derive(Arbitrary, Debug, PartialEq)] 312 | #[arbitrary(bound(T : Arbitrary + 'static))] 313 | struct TestInput { 314 | #[strategy(any_with::(Default::default()))] 315 | x: T, 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /tests/readme_proptest.rs: -------------------------------------------------------------------------------- 1 | use test_strategy::proptest; 2 | 3 | #[proptest] 4 | fn my_test(_input: i32) { 5 | // ... 6 | } 7 | 8 | #[proptest] 9 | fn my_test2(#[strategy(10..20)] _input: i32) { 10 | // ... 11 | } 12 | 13 | use proptest::prelude::ProptestConfig; 14 | #[proptest(ProptestConfig { cases : 1000, ..ProptestConfig::default() })] 15 | fn my_test_with_config(_input: i32) { 16 | // ... 17 | } 18 | 19 | #[proptest(ProptestConfig::default(), cases = 1000)] 20 | fn my_test_with_config_2(_input: i32) { 21 | // ... 22 | } 23 | #[proptest(cases = 1000)] 24 | fn my_test_with_config_3(_input: i32) { 25 | // ... 26 | } 27 | 28 | // #[proptest] 29 | // #[proptest_dump] 30 | // fn my_test(_input: i32) { 31 | // // ... 32 | // } 33 | -------------------------------------------------------------------------------- /tests/test_helpers.rs: -------------------------------------------------------------------------------- 1 | use proptest::{ 2 | arbitrary::any, arbitrary::Arbitrary, strategy::Strategy, strategy::ValueTree, 3 | test_runner::TestRunner, 4 | }; 5 | use std::fmt::Debug; 6 | 7 | enum Op { 8 | Simplify, 9 | Complicate, 10 | } 11 | 12 | #[track_caller] 13 | pub fn assert_arbitrary(e: impl Strategy) { 14 | assert_eq_strategy(any::(), e); 15 | } 16 | 17 | #[track_caller] 18 | pub fn assert_eq_strategy( 19 | l: impl Strategy, 20 | r: impl Strategy, 21 | ) { 22 | let mut ops = vec![Op::Simplify]; 23 | for _ in 0..1000 { 24 | ops.push(Op::Simplify); 25 | ops.push(Op::Complicate); 26 | } 27 | assert_eq_strategy_ops(l, r, ops) 28 | } 29 | 30 | #[track_caller] 31 | fn assert_eq_strategy_ops( 32 | l: impl Strategy, 33 | r: impl Strategy, 34 | ops: Vec, 35 | ) { 36 | let mut l_runner = TestRunner::deterministic(); 37 | let mut r_runner = TestRunner::deterministic(); 38 | 39 | let mut r_tree = r 40 | .new_tree(&mut r_runner) 41 | .unwrap_or_else(|e| panic!("r.new_tree failed: {:?}", e)); 42 | let mut l_tree = l 43 | .new_tree(&mut l_runner) 44 | .unwrap_or_else(|e| panic!("l.new_tree failed: {:?}", e)); 45 | 46 | let mut step = 0; 47 | for op in ops { 48 | let l_value = l_tree.current(); 49 | let r_value = r_tree.current(); 50 | assert_eq!(l_value, r_value, "value: {step}"); 51 | step += 1; 52 | match op { 53 | Op::Simplify => assert_eq!(l_tree.simplify(), r_tree.simplify(), "step: {step}"), 54 | Op::Complicate => assert_eq!(l_tree.complicate(), r_tree.complicate(), "step: {step}"), 55 | } 56 | } 57 | } 58 | --------------------------------------------------------------------------------