├── .github └── workflows │ └── rust.yml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── derive_fuzztest ├── Cargo.toml ├── fuzz │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── bin │ │ ├── arbitrary.rs │ │ ├── discard_result.rs │ │ ├── integer_add.rs │ │ ├── strange_argument_patterns.rs │ │ └── strange_argument_patterns2.rs ├── src │ └── lib.rs └── tests │ └── run_fuzz.rs └── derive_fuzztest_macro ├── Cargo.toml ├── README.md └── src └── lib.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: cargo build and test 2 | permissions: read-all 3 | 4 | on: 5 | push: 6 | branches: [ "main" ] 7 | pull_request: 8 | branches: [ "main" ] 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | build-ubuntu: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 20 | - uses: dtolnay/rust-toolchain@64dec968ae28eba8cb36254ca81ecc9d3249ffd4 # nightly, as of 2024-07-26 21 | with: 22 | toolchain: nightly 23 | - name: Install cargo-fuzz 24 | run: cargo install cargo-fuzz@0.12.0 25 | - name: Build 26 | run: cargo build --verbose 27 | - name: Run tests 28 | run: cargo test --verbose 29 | 30 | build-windows: 31 | 32 | runs-on: windows-latest 33 | 34 | steps: 35 | - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 36 | - name: Build 37 | run: cargo build --verbose 38 | - name: Run tests 39 | run: cargo test --verbose 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | We'd love to accept your patches and contributions to this project. 4 | 5 | ## Before you begin 6 | 7 | ### Sign our Contributor License Agreement 8 | 9 | Contributions to this project must be accompanied by a 10 | [Contributor License Agreement](https://cla.developers.google.com/about) (CLA). 11 | You (or your employer) retain the copyright to your contribution; this simply 12 | gives us permission to use and redistribute your contributions as part of the 13 | project. 14 | 15 | If you or your current employer have already signed the Google CLA (even if it 16 | was for a different project), you probably don't need to do it again. 17 | 18 | Visit to see your current agreements or to 19 | sign a new one. 20 | 21 | ### Review our community guidelines 22 | 23 | This project follows 24 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/). 25 | 26 | ## Contribution process 27 | 28 | ### Code reviews 29 | 30 | All submissions, including submissions by project members, require review. We 31 | use GitHub pull requests for this purpose. Consult 32 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 33 | information on using pull requests. 34 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.86" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 19 | 20 | [[package]] 21 | name = "arbitrary" 22 | version = "1.3.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" 25 | dependencies = [ 26 | "derive_arbitrary", 27 | ] 28 | 29 | [[package]] 30 | name = "autocfg" 31 | version = "1.2.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" 34 | 35 | [[package]] 36 | name = "bit-set" 37 | version = "0.5.3" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" 40 | dependencies = [ 41 | "bit-vec", 42 | ] 43 | 44 | [[package]] 45 | name = "bit-vec" 46 | version = "0.6.3" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 49 | 50 | [[package]] 51 | name = "bitflags" 52 | version = "2.5.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 55 | 56 | [[package]] 57 | name = "cc" 58 | version = "1.0.90" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" 61 | dependencies = [ 62 | "jobserver", 63 | "libc", 64 | ] 65 | 66 | [[package]] 67 | name = "cfg-if" 68 | version = "1.0.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 71 | 72 | [[package]] 73 | name = "derive_arbitrary" 74 | version = "1.3.2" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" 77 | dependencies = [ 78 | "proc-macro2", 79 | "quote", 80 | "syn", 81 | ] 82 | 83 | [[package]] 84 | name = "derive_fuzz_example" 85 | version = "0.1.4" 86 | dependencies = [ 87 | "arbitrary", 88 | "derive_fuzztest", 89 | "libfuzzer-sys", 90 | "quickcheck", 91 | ] 92 | 93 | [[package]] 94 | name = "derive_fuzztest" 95 | version = "0.1.4" 96 | dependencies = [ 97 | "anyhow", 98 | "arbitrary", 99 | "derive_fuzztest_macro", 100 | "libfuzzer-sys", 101 | "proptest", 102 | "proptest-arbitrary-interop", 103 | "quickcheck", 104 | "xshell", 105 | ] 106 | 107 | [[package]] 108 | name = "derive_fuzztest_macro" 109 | version = "0.1.4" 110 | dependencies = [ 111 | "pretty_assertions", 112 | "prettyplease", 113 | "proc-macro2", 114 | "quote", 115 | "syn", 116 | ] 117 | 118 | [[package]] 119 | name = "diff" 120 | version = "0.1.13" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" 123 | 124 | [[package]] 125 | name = "env_logger" 126 | version = "0.8.4" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" 129 | dependencies = [ 130 | "log", 131 | "regex", 132 | ] 133 | 134 | [[package]] 135 | name = "errno" 136 | version = "0.3.8" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 139 | dependencies = [ 140 | "libc", 141 | "windows-sys", 142 | ] 143 | 144 | [[package]] 145 | name = "fastrand" 146 | version = "2.0.2" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" 149 | 150 | [[package]] 151 | name = "fnv" 152 | version = "1.0.7" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 155 | 156 | [[package]] 157 | name = "getrandom" 158 | version = "0.2.12" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" 161 | dependencies = [ 162 | "cfg-if", 163 | "libc", 164 | "wasi", 165 | ] 166 | 167 | [[package]] 168 | name = "jobserver" 169 | version = "0.1.28" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" 172 | dependencies = [ 173 | "libc", 174 | ] 175 | 176 | [[package]] 177 | name = "lazy_static" 178 | version = "1.4.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 181 | 182 | [[package]] 183 | name = "libc" 184 | version = "0.2.153" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 187 | 188 | [[package]] 189 | name = "libfuzzer-sys" 190 | version = "0.4.7" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" 193 | dependencies = [ 194 | "arbitrary", 195 | "cc", 196 | "once_cell", 197 | ] 198 | 199 | [[package]] 200 | name = "libm" 201 | version = "0.2.8" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" 204 | 205 | [[package]] 206 | name = "linux-raw-sys" 207 | version = "0.4.13" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" 210 | 211 | [[package]] 212 | name = "log" 213 | version = "0.4.21" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 216 | 217 | [[package]] 218 | name = "memchr" 219 | version = "2.7.2" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 222 | 223 | [[package]] 224 | name = "num-traits" 225 | version = "0.2.18" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" 228 | dependencies = [ 229 | "autocfg", 230 | "libm", 231 | ] 232 | 233 | [[package]] 234 | name = "once_cell" 235 | version = "1.19.0" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 238 | 239 | [[package]] 240 | name = "ppv-lite86" 241 | version = "0.2.17" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 244 | 245 | [[package]] 246 | name = "pretty_assertions" 247 | version = "1.4.0" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" 250 | dependencies = [ 251 | "diff", 252 | "yansi", 253 | ] 254 | 255 | [[package]] 256 | name = "prettyplease" 257 | version = "0.2.17" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" 260 | dependencies = [ 261 | "proc-macro2", 262 | "syn", 263 | ] 264 | 265 | [[package]] 266 | name = "proc-macro2" 267 | version = "1.0.79" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" 270 | dependencies = [ 271 | "unicode-ident", 272 | ] 273 | 274 | [[package]] 275 | name = "proptest" 276 | version = "1.4.0" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" 279 | dependencies = [ 280 | "bit-set", 281 | "bit-vec", 282 | "bitflags", 283 | "lazy_static", 284 | "num-traits", 285 | "rand", 286 | "rand_chacha", 287 | "rand_xorshift", 288 | "regex-syntax", 289 | "rusty-fork", 290 | "tempfile", 291 | "unarray", 292 | ] 293 | 294 | [[package]] 295 | name = "proptest-arbitrary-interop" 296 | version = "0.1.0" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "a1981e49bd2432249da8b0e11e5557099a8e74690d6b94e721f7dc0bb7f3555f" 299 | dependencies = [ 300 | "arbitrary", 301 | "proptest", 302 | ] 303 | 304 | [[package]] 305 | name = "quick-error" 306 | version = "1.2.3" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 309 | 310 | [[package]] 311 | name = "quickcheck" 312 | version = "1.0.3" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" 315 | dependencies = [ 316 | "env_logger", 317 | "log", 318 | "rand", 319 | ] 320 | 321 | [[package]] 322 | name = "quote" 323 | version = "1.0.35" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 326 | dependencies = [ 327 | "proc-macro2", 328 | ] 329 | 330 | [[package]] 331 | name = "rand" 332 | version = "0.8.5" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 335 | dependencies = [ 336 | "libc", 337 | "rand_chacha", 338 | "rand_core", 339 | ] 340 | 341 | [[package]] 342 | name = "rand_chacha" 343 | version = "0.3.1" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 346 | dependencies = [ 347 | "ppv-lite86", 348 | "rand_core", 349 | ] 350 | 351 | [[package]] 352 | name = "rand_core" 353 | version = "0.6.4" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 356 | dependencies = [ 357 | "getrandom", 358 | ] 359 | 360 | [[package]] 361 | name = "rand_xorshift" 362 | version = "0.3.0" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" 365 | dependencies = [ 366 | "rand_core", 367 | ] 368 | 369 | [[package]] 370 | name = "regex" 371 | version = "1.10.4" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" 374 | dependencies = [ 375 | "aho-corasick", 376 | "memchr", 377 | "regex-automata", 378 | "regex-syntax", 379 | ] 380 | 381 | [[package]] 382 | name = "regex-automata" 383 | version = "0.4.6" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" 386 | dependencies = [ 387 | "aho-corasick", 388 | "memchr", 389 | "regex-syntax", 390 | ] 391 | 392 | [[package]] 393 | name = "regex-syntax" 394 | version = "0.8.3" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" 397 | 398 | [[package]] 399 | name = "rustix" 400 | version = "0.38.32" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" 403 | dependencies = [ 404 | "bitflags", 405 | "errno", 406 | "libc", 407 | "linux-raw-sys", 408 | "windows-sys", 409 | ] 410 | 411 | [[package]] 412 | name = "rusty-fork" 413 | version = "0.3.0" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" 416 | dependencies = [ 417 | "fnv", 418 | "quick-error", 419 | "tempfile", 420 | "wait-timeout", 421 | ] 422 | 423 | [[package]] 424 | name = "syn" 425 | version = "2.0.58" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" 428 | dependencies = [ 429 | "proc-macro2", 430 | "quote", 431 | "unicode-ident", 432 | ] 433 | 434 | [[package]] 435 | name = "tempfile" 436 | version = "3.10.1" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" 439 | dependencies = [ 440 | "cfg-if", 441 | "fastrand", 442 | "rustix", 443 | "windows-sys", 444 | ] 445 | 446 | [[package]] 447 | name = "unarray" 448 | version = "0.1.4" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" 451 | 452 | [[package]] 453 | name = "unicode-ident" 454 | version = "1.0.12" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 457 | 458 | [[package]] 459 | name = "wait-timeout" 460 | version = "0.2.0" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" 463 | dependencies = [ 464 | "libc", 465 | ] 466 | 467 | [[package]] 468 | name = "wasi" 469 | version = "0.11.0+wasi-snapshot-preview1" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 472 | 473 | [[package]] 474 | name = "windows-sys" 475 | version = "0.52.0" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 478 | dependencies = [ 479 | "windows-targets", 480 | ] 481 | 482 | [[package]] 483 | name = "windows-targets" 484 | version = "0.52.4" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" 487 | dependencies = [ 488 | "windows_aarch64_gnullvm", 489 | "windows_aarch64_msvc", 490 | "windows_i686_gnu", 491 | "windows_i686_msvc", 492 | "windows_x86_64_gnu", 493 | "windows_x86_64_gnullvm", 494 | "windows_x86_64_msvc", 495 | ] 496 | 497 | [[package]] 498 | name = "windows_aarch64_gnullvm" 499 | version = "0.52.4" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" 502 | 503 | [[package]] 504 | name = "windows_aarch64_msvc" 505 | version = "0.52.4" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" 508 | 509 | [[package]] 510 | name = "windows_i686_gnu" 511 | version = "0.52.4" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" 514 | 515 | [[package]] 516 | name = "windows_i686_msvc" 517 | version = "0.52.4" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" 520 | 521 | [[package]] 522 | name = "windows_x86_64_gnu" 523 | version = "0.52.4" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" 526 | 527 | [[package]] 528 | name = "windows_x86_64_gnullvm" 529 | version = "0.52.4" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" 532 | 533 | [[package]] 534 | name = "windows_x86_64_msvc" 535 | version = "0.52.4" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" 538 | 539 | [[package]] 540 | name = "xshell" 541 | version = "0.2.6" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "6db0ab86eae739efd1b054a8d3d16041914030ac4e01cd1dca0cf252fd8b6437" 544 | dependencies = [ 545 | "xshell-macros", 546 | ] 547 | 548 | [[package]] 549 | name = "xshell-macros" 550 | version = "0.2.6" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "9d422e8e38ec76e2f06ee439ccc765e9c6a9638b9e7c9f2e8255e4d41e8bd852" 553 | 554 | [[package]] 555 | name = "yansi" 556 | version = "0.5.1" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" 559 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "derive_fuzztest", 4 | "derive_fuzztest/fuzz", 5 | "derive_fuzztest_macro", 6 | ] 7 | resolver = "2" 8 | 9 | [workspace.lints.rust] 10 | missing_docs = "deny" 11 | trivial_casts = "deny" 12 | trivial_numeric_casts = "deny" 13 | unsafe_code = "deny" 14 | unsafe_op_in_unsafe_fn = "deny" 15 | unused_extern_crates = "deny" 16 | unused_import_braces = "deny" 17 | unused_results = "deny" 18 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } 19 | 20 | [workspace.lints.clippy] 21 | expect_used = "deny" 22 | indexing_slicing = "deny" 23 | panic = "deny" 24 | unwrap_used = "deny" 25 | 26 | [workspace.dependencies] 27 | # local crates 28 | derive_fuzztest = { version = "0.1.4", path = "derive_fuzztest" } 29 | derive_fuzztest_macro = { version = "0.1.4", path = "derive_fuzztest_macro" } 30 | 31 | # from crates.io 32 | arbitrary = "1.3.2" 33 | libfuzzer-sys = "0.4.7" 34 | pretty_assertions = "1.4.0" 35 | prettyplease = "0.2.16" 36 | proc-macro2 = "1.0" 37 | proptest = "1.4.0" 38 | proptest-arbitrary-interop = "0.1.0" 39 | quickcheck = "1.0.3" 40 | quote = "1.0" 41 | syn = { version = "2.0", features = ["full"] } 42 | 43 | [workspace.package] 44 | version = "0.1.4" 45 | edition = "2021" 46 | license = "Apache-2.0" 47 | publish = true 48 | categories = ["development-tools::testing"] 49 | keywords = ["fuzz", "property testing"] 50 | repository = "https://github.com/google/rust-derive-fuzztest" 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | derive_fuzztest 2 | ========== 3 | 4 | _This is not an officially supported Google product._ 5 | 6 | [![Crates.io](https://img.shields.io/crates/v/derive_fuzztest)](https://crates.io/crates/derive_fuzztest) [![Docs](https://docs.rs/derive_fuzztest/badge.svg)](https://docs.rs/derive_fuzztest) 7 | 8 | ## Description 9 | 10 | Proc macros that generates both a fuzz target for use with `cargo fuzz`, and a property test 11 | (via `quickcheck` or `proptest`) for use with `cargo test`. 12 | 13 | The reason for having both is that property testing allows for quick iteration to make sure the 14 | test works, and can be checked in presubmit CI, while fuzzing can test the input space more 15 | exhaustively and run continuously. 16 | 17 | ## Example 18 | 19 | ```rust 20 | #![cfg_attr(fuzzing, no_main)] 21 | 22 | #[derive_fuzztest::fuzztest] 23 | fn transitive_ord(a: u32, b: u32, c: u32) { 24 | if a >= b && b >= c { 25 | assert!(a >= c); 26 | } 27 | if a <= b && b <= c { 28 | assert!(a <= c); 29 | } 30 | } 31 | 32 | #[test] 33 | fn additional_test_here() { 34 | /* ... */ 35 | } 36 | ``` 37 | 38 | # Result reporting 39 | 40 | Test functions report test failures by panicking, similar to how you would write a regular 41 | `#[test]`. If the annotated test function completes without panicking, the test is considered to 42 | have passed. 43 | 44 | In some cases, you may want to discard some inputs, treating them neither as passes nor failures, 45 | just continue onto testing another generated input. This can be done by returning a 46 | [`TestResult`](https://docs.rs/derive_fuzztest/latest/derive_fuzztest/enum.TestResult.html) from the 47 | annotated function. Property testing frameworks will try to generate more test cases to replace the 48 | discarded one, up to a certain limit. For fuzzing, test results that are discarded will not be added 49 | to the corpus. 50 | 51 | ```rust 52 | use derive_fuzztest::TestResult; 53 | 54 | #[derive_fuzztest::fuzztest] 55 | fn increment(a: u8) -> TestResult { 56 | if a < u8::MAX { 57 | assert_eq!(a + 1 - 1, a); 58 | TestResult::Passed 59 | } else { 60 | TestResult::Discard 61 | } 62 | } 63 | ``` 64 | 65 | ## Usage 66 | 67 | Run the generated property tests 68 | ```sh 69 | cargo test 70 | ``` 71 | 72 | Run continuous fuzzing 73 | ```sh 74 | cargo install cargo-fuzz 75 | cargo +nightly fuzz run 76 | ``` 77 | 78 | ## Crate structure 79 | 80 | If you use `#[fuzz]` or `#[fuzztest]`, the fuzz target imposes the following requirements: 81 | 82 | * The target must be in a separate `[[bin]]` target that only contains a single fuzz target. 83 | * The crate containing the bin target has `[package.metadata] cargo-fuzz = true` 84 | * The bin target is annotated with `#![cfg_attr(fuzzing, no_main)]` 85 | 86 | The recommended structure for your crate `foo` is to put your tests under `foo/fuzz/src/bin`: 87 | 88 | ```text 89 | foo 90 | ├── fuzz 91 | │ ├── src 92 | │ │ └── bin 93 | │ │ └── fuzz_target_1.rs 94 | │ └── Cargo.toml 95 | ├── src 96 | │ └── [project source] 97 | └── Cargo.toml 98 | ``` 99 | 100 | This is different from the default structure generated by `cargo fuzz init` or `cargo fuzz add` 101 | so that we can take advantage of [target 102 | auto-discovery](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#target-auto-discovery). 103 | If you prefer, the default structure generated by `cargo fuzz` can also work, but make sure you 104 | remove `test = false` from the generated target in `Cargo.toml`. 105 | 106 | You will also need to declare a dependency on the `libfuzzer-sys` crate, but only if fuzzing is 107 | requested: 108 | 109 | ```toml 110 | [target.'cfg(fuzzing)'.dependencies] 111 | libfuzzer-sys = "*" 112 | ``` 113 | 114 | (The reason for this conditional dependency is that `libfuzzer-sys` injects a main function to 115 | the resulting binary, and there will be linking failures if we link that in without defining a 116 | corresponding `fuzz_target`.) 117 | 118 | ## Features 119 | 120 | * `quickcheck` (default) — Enable generation of 121 | [`quickcheck`](https://docs.rs/quickcheck/latest/quickcheck/) property tests. 122 | * `proptest` — Enable generation of [`proptest`](https://docs.rs/proptest/latest/proptest/) 123 | property tests. 124 | 125 | ## Relevant reading 126 | * [Announcing Better Support for Fuzzing with Structured Inputs in 127 | Rust](https://fitzgeraldnick.com/2020/01/16/better-support-for-fuzzing-structured-inputs-in-rust.html#how-is-all-this-different-from-quickcheck-and-proptest) 128 | * [Bridging Fuzzing and Property 129 | Testing](https://blog.yoshuawuyts.com/bridging-fuzzing-and-property-testing/) 130 | -------------------------------------------------------------------------------- /derive_fuzztest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "derive_fuzztest" 3 | description = "A Rust proc-macro to enable using the same implementation for fuzz tests and property tests." 4 | version.workspace = true 5 | edition.workspace = true 6 | publish.workspace = true 7 | license.workspace = true 8 | categories.workspace = true 9 | repository.workspace = true 10 | readme = "../README.md" 11 | 12 | [dependencies] 13 | arbitrary.workspace = true 14 | derive_fuzztest_macro.workspace = true 15 | proptest = { workspace = true, optional = true } 16 | proptest-arbitrary-interop = { workspace = true, optional = true } 17 | quickcheck = { workspace = true, optional = true } 18 | 19 | [target.'cfg(fuzzing)'.dependencies] 20 | libfuzzer-sys.workspace = true 21 | 22 | [features] 23 | default = ["quickcheck"] 24 | quickcheck = ["dep:quickcheck", "derive_fuzztest_macro/quickcheck"] 25 | proptest = [ 26 | "dep:proptest", 27 | "dep:proptest-arbitrary-interop", 28 | "derive_fuzztest_macro/proptest", 29 | ] 30 | all = ["quickcheck", "proptest"] 31 | 32 | [lints] 33 | workspace = true 34 | 35 | [dev-dependencies] 36 | anyhow = "1.0.86" 37 | xshell = "0.2.6" 38 | 39 | [package.metadata.docs.rs] 40 | # Generate docs for all features. 41 | all-features = true 42 | -------------------------------------------------------------------------------- /derive_fuzztest/fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | /corpus -------------------------------------------------------------------------------- /derive_fuzztest/fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "derive_fuzz_example" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | categories.workspace = true 7 | publish = false 8 | 9 | [package.metadata] 10 | cargo-fuzz = true 11 | 12 | [dependencies] 13 | arbitrary = { workspace = true, features = ["derive"] } 14 | derive_fuzztest = { workspace = true, features = ["all"]} 15 | quickcheck.workspace = true 16 | 17 | [target.'cfg(fuzzing)'.dependencies] 18 | libfuzzer-sys.workspace = true 19 | 20 | [lints.rust] 21 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } 22 | -------------------------------------------------------------------------------- /derive_fuzztest/fuzz/src/bin/arbitrary.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![cfg_attr(fuzzing, no_main)] 16 | #![no_std] 17 | 18 | use arbitrary::{Arbitrary, Unstructured}; 19 | use derive_fuzztest::fuzztest; 20 | 21 | #[derive(Debug, Clone)] 22 | pub struct SmallU8(u8); 23 | 24 | impl<'a> Arbitrary<'a> for SmallU8 { 25 | fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { 26 | Ok(SmallU8(u.int_in_range(0..=127)?)) 27 | } 28 | } 29 | 30 | #[fuzztest] 31 | pub fn test(a: SmallU8, b: SmallU8) { 32 | let _ = a.0 + b.0; // Succeeds because our custom arbitrary impl only generates 0-127 so it never overflows 33 | } 34 | -------------------------------------------------------------------------------- /derive_fuzztest/fuzz/src/bin/discard_result.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![cfg_attr(fuzzing, no_main)] 16 | #![no_std] 17 | 18 | use derive_fuzztest::{fuzztest, TestResult}; 19 | 20 | #[fuzztest] 21 | pub fn test(a: [u8; 5]) -> TestResult { 22 | if a[0].is_ascii_lowercase() { 23 | TestResult::Passed 24 | } else { 25 | // Discard any test results with first character not a lowercase ASCII. 26 | // To verify: 27 | // - For fuzzing, you can verify that by opening the `corpus` directory. 28 | // - For proptest, `PROPTEST_MAX_GLOBAL_REJECTS=1 cargo test` will fail. 29 | // - For quickcheck, `QUICKCHECK_MIN_TESTS_PASSED=1024 cargo test` will fail. 30 | TestResult::Discard 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /derive_fuzztest/fuzz/src/bin/integer_add.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![cfg_attr(fuzzing, no_main)] 16 | #![no_std] 17 | 18 | use derive_fuzztest::fuzztest; 19 | 20 | #[fuzztest] 21 | pub fn test(a: u8, b: u8) { 22 | let _ = a.checked_add(b); 23 | // a + b; // This fails because a + b can overflow. 24 | } 25 | -------------------------------------------------------------------------------- /derive_fuzztest/fuzz/src/bin/strange_argument_patterns.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![cfg_attr(fuzzing, no_main)] 16 | #![no_std] 17 | 18 | use derive_fuzztest::fuzztest; 19 | 20 | /// Test case to make sure the code generation works for unusual patterns in the function 21 | /// parameters. 22 | #[fuzztest] 23 | fn test( 24 | a: u8, 25 | mut b: u8, 26 | (ref c, mut d, _): (u8, u8, u8), 27 | r#try: (), 28 | 0..: u8, 29 | 0..=255: u8, 30 | (..): (), 31 | (..): (u8, u8, u8), 32 | ) { 33 | } 34 | -------------------------------------------------------------------------------- /derive_fuzztest/fuzz/src/bin/strange_argument_patterns2.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![cfg_attr(fuzzing, no_main)] 16 | #![no_std] 17 | 18 | use arbitrary::Arbitrary; 19 | use derive_fuzztest::fuzztest; 20 | 21 | #[derive(Arbitrary, Clone, Debug)] 22 | struct UnitStruct; 23 | 24 | #[derive(Arbitrary, Clone, Debug)] 25 | struct TestStruct { 26 | field1: u8, 27 | field2: u8, 28 | } 29 | 30 | #[derive(Arbitrary, Clone, Debug)] 31 | enum Either { 32 | A { a: u8 }, 33 | B { b: u8 }, 34 | } 35 | 36 | /// Test case to make sure the code generation works for unusual patterns in the function 37 | /// parameters. 38 | #[fuzztest] 39 | fn test( 40 | UnitStruct: UnitStruct, 41 | TestStruct { field1, mut field2 }: TestStruct, 42 | (Either::A { a } | Either::B { b: a }): Either, 43 | (a2 @ Either::A { a: _ } | a2 @ Either::B { .. }): Either, 44 | [i1, i2, i3, ..]: [u8; 12], 45 | ) { 46 | } 47 | -------------------------------------------------------------------------------- /derive_fuzztest/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Derive macros that generates both a fuzz target for use with `cargo fuzz`, and a property test 16 | //! (via `quickcheck` or `proptest`) for use with `cargo test`. 17 | //! 18 | //! The reason for having both is that property testing allows for quick iteration to make sure the 19 | //! test works, and can be checked in presubmit CI, while fuzzing can test the input space more 20 | //! exhaustively and run continuously. 21 | //! 22 | //! # Example 23 | //! 24 | //! ```no_run 25 | //! #![cfg_attr(fuzzing, no_main)] 26 | //! 27 | //! #[derive_fuzztest::fuzztest] 28 | //! fn transitive_ord(a: u32, b: u32, c: u32) { 29 | //! if a >= b && b >= c { 30 | //! assert!(a >= c); 31 | //! } 32 | //! if a <= b && b <= c { 33 | //! assert!(a <= c); 34 | //! } 35 | //! } 36 | //! 37 | //! #[test] 38 | //! fn additional_test_here() { 39 | //! /* ... */ 40 | //! } 41 | //! ``` 42 | //! 43 | //! # Result reporting 44 | //! 45 | //! Test functions report test failures by panicking, similar to how you would write a regular 46 | //! `#[test]`. If the annotated test function completes without panicking, the test is considered to 47 | //! have passed. 48 | //! 49 | //! In some cases, you may want to discard some inputs, treating them neither as passes nor 50 | //! failures, just continue onto testing another generated input. This can be done by returning a 51 | //! [`TestResult`] from the annotated function. Property testing frameworks will try to generate 52 | //! more test cases to replace the discarded one, up to a certain limit. For fuzzing, test results 53 | //! that are discarded will not be added to the corpus. 54 | //! 55 | //! ``` 56 | //! use derive_fuzztest::TestResult; 57 | //! 58 | //! #[derive_fuzztest::fuzztest] 59 | //! fn increment(a: u8) -> TestResult { 60 | //! if a < u8::MAX { 61 | //! assert_eq!(a + 1 - 1, a); 62 | //! TestResult::Passed 63 | //! } else { 64 | //! TestResult::Discard 65 | //! } 66 | //! } 67 | //! ``` 68 | //! 69 | //! # Usage 70 | //! 71 | //! 72 | //! Run the generated property tests 73 | //! ```sh 74 | //! cargo test 75 | //! ``` 76 | //! 77 | //! Run continuous fuzzing 78 | //! ```sh 79 | //! cargo +nightly fuzz run 80 | //! ``` 81 | //! 82 | //! # Crate structure 83 | //! 84 | //! If you use `#[fuzz]` or `#[fuzztest]`, the fuzz target imposes the following requirements: 85 | //! 86 | //! * The target must be in a separate `[[bin]]` target that only contains a single fuzz target. 87 | //! * The crate containing the bin target has `[package.metadata] cargo-fuzz = true` 88 | //! * The bin target is annotated with `#![cfg_attr(fuzzing, no_main)]` 89 | //! 90 | //! The recommended structure for your crate `foo` is to put your tests under `foo/fuzz/src/bin`: 91 | //! 92 | //! ```text 93 | //! foo 94 | //! ├── fuzz 95 | //! │ ├── src 96 | //! │ │ └── bin 97 | //! │ │ └── fuzz_target_1.rs 98 | //! │ └── Cargo.toml 99 | //! ├── src 100 | //! │ └── [project source] 101 | //! └── Cargo.toml 102 | //! ``` 103 | //! 104 | //! This is different from the default structure generated by `cargo fuzz init` or `cargo fuzz add` 105 | //! so that we can take advantage of [target 106 | //! auto-discovery](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#target-auto-discovery). 107 | //! If you prefer, the default structure generated by `cargo fuzz` can also work, but make sure you 108 | //! remove `test = false` from the generated target in `Cargo.toml`. 109 | //! 110 | //! You will also need to declare a dependency on the `libfuzzer-sys` crate, but only if fuzzing is 111 | //! requested: 112 | //! 113 | //! ```toml 114 | //! [target.'cfg(fuzzing)'.dependencies] 115 | //! libfuzzer-sys = "*" 116 | //! ``` 117 | //! 118 | //! (The reason for this conditional dependency is that `libfuzzer-sys` injects a main function to 119 | //! the resulting binary, and there will be linking failures if we link that in without defining a 120 | //! corresponding `fuzz_target`.) 121 | //! 122 | //! # Features 123 | //! 124 | //! * `quickcheck` (default) — Enable generation of 125 | //! [`quickcheck`](https://docs.rs/quickcheck/latest/quickcheck/) property tests. 126 | //! * `proptest` — Enable generation of [`proptest`](https://docs.rs/proptest/latest/proptest/) 127 | //! property tests. 128 | //! 129 | //! #### See also 130 | //! * [Announcing Better Support for Fuzzing with Structured Inputs in 131 | //! Rust](https://fitzgeraldnick.com/2020/01/16/better-support-for-fuzzing-structured-inputs-in-rust.html#how-is-all-this-different-from-quickcheck-and-proptest) 132 | //! * [Bridging Fuzzing and Property 133 | //! Testing](https://blog.yoshuawuyts.com/bridging-fuzzing-and-property-testing/) 134 | 135 | pub use derive_fuzztest_macro::{fuzz, fuzztest, proptest}; 136 | 137 | #[doc(hidden)] 138 | pub mod reexport { 139 | #[cfg(feature = "proptest")] 140 | pub use proptest; 141 | #[cfg(feature = "proptest")] 142 | pub use proptest_arbitrary_interop; 143 | #[cfg(feature = "quickcheck")] 144 | pub use quickcheck; 145 | } 146 | 147 | /// A test result reported from the test case. 148 | /// 149 | /// A test case annotated with `#[fuzztest]`, `#[fuzz]`, or `#[proptest]` can optionally return a 150 | /// `TestResult` to indicate whether a test should be treated as discarded or passed. 151 | /// 152 | /// If the test does not have a return value, all non-panicking test cases default to passed. 153 | pub enum TestResult { 154 | /// Indicates that a test passed. 155 | Passed, 156 | /// Indicates that a test should be discarded. 157 | /// 158 | /// For property testing, discarding a test result will cause `quickcheck` or `proptest` to try 159 | /// to generate more test cases to find non-discarded ones, up to a certain limit set by the 160 | /// framework. This corresponds to [`quickcheck::TestResult::discard`] and 161 | /// [`proptest::test_runner::TestCaseError::Reject`](https://docs.rs/proptest/latest/proptest/test_runner/enum.TestCaseError.html#variant.Reject). 162 | /// 163 | /// For fuzzing, a discarded result will not be added to the corpus, corresponding to 164 | /// [`libfuzzer_sys::Corpus::Reject`](https://docs.rs/libfuzzer-sys/latest/libfuzzer_sys/enum.Corpus.html#variant.Reject). 165 | Discard, 166 | } 167 | 168 | impl From<()> for TestResult { 169 | fn from(_: ()) -> Self { 170 | TestResult::Passed 171 | } 172 | } 173 | 174 | #[cfg(fuzzing)] 175 | impl From for ::libfuzzer_sys::Corpus { 176 | fn from(test_result: TestResult) -> Self { 177 | match test_result { 178 | TestResult::Passed => Self::Keep, 179 | TestResult::Discard => Self::Reject, 180 | } 181 | } 182 | } 183 | 184 | #[cfg(feature = "proptest")] 185 | impl From for proptest::test_runner::TestCaseResult { 186 | fn from(value: TestResult) -> Self { 187 | use proptest::{prelude::TestCaseError, test_runner::TestCaseResult}; 188 | match value { 189 | TestResult::Passed => TestCaseResult::Ok(()), 190 | TestResult::Discard => { 191 | TestCaseResult::Err(TestCaseError::reject("Discarded by test case")) 192 | } 193 | } 194 | } 195 | } 196 | 197 | #[cfg(feature = "quickcheck")] 198 | impl From for quickcheck::TestResult { 199 | fn from(value: TestResult) -> Self { 200 | match value { 201 | TestResult::Passed => quickcheck::TestResult::passed(), 202 | TestResult::Discard => quickcheck::TestResult::discard(), 203 | } 204 | } 205 | } 206 | 207 | #[cfg(feature = "quickcheck")] 208 | #[doc(hidden)] 209 | pub mod arbitrary_bridge { 210 | use std::fmt::{Debug, Formatter}; 211 | 212 | /// Wrapper type that allows `arbitrary::Arbitrary` to be used as `quickcheck::Arbitrary` 213 | #[derive(Clone)] 214 | pub struct ArbitraryAdapter arbitrary::Arbitrary<'a>>( 215 | pub Result, 216 | ); 217 | 218 | impl Debug for ArbitraryAdapter 219 | where 220 | T: for<'a> arbitrary::Arbitrary<'a> + Debug, 221 | { 222 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 223 | match &self.0 { 224 | Ok(v) => v.fmt(f), 225 | Err(e) => e.fmt(f), 226 | } 227 | } 228 | } 229 | 230 | impl quickcheck::Arbitrary for ArbitraryAdapter 231 | where 232 | T: for<'a> arbitrary::Arbitrary<'a> + Clone + 'static, 233 | { 234 | fn arbitrary(g: &mut quickcheck::Gen) -> Self { 235 | let bytes = Vec::::arbitrary(g); 236 | let mut unstructured = arbitrary::Unstructured::new(&bytes); 237 | Self(T::arbitrary(&mut unstructured)) 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /derive_fuzztest/tests/run_fuzz.rs: -------------------------------------------------------------------------------- 1 | //! Runs the fuzzers with `cargo fuzz run` 2 | 3 | #![allow(clippy::unwrap_used)] 4 | 5 | use std::path::PathBuf; 6 | 7 | use xshell::cmd; 8 | 9 | // Windows fuzz runs currently fail with STATUS_DLL_NOT_FOUND 10 | #[cfg(not(target_family = "windows"))] 11 | #[test] 12 | fn run_fuzzers() -> anyhow::Result<()> { 13 | let sh = xshell::Shell::new()?; 14 | sh.change_dir(PathBuf::from(file!()).parent().unwrap().parent().unwrap().parent().unwrap().join("fuzz")); 15 | cmd!(sh, "cargo +nightly fuzz run arbitrary -- -runs=1000").run()?; 16 | cmd!(sh, "cargo +nightly fuzz run discard_result -- -runs=1000").run()?; 17 | cmd!(sh, "cargo +nightly fuzz run integer_add -- -runs=1000").run()?; 18 | cmd!(sh, "cargo +nightly fuzz run strange_argument_patterns -- -runs=1000").run()?; 19 | Ok(()) 20 | } -------------------------------------------------------------------------------- /derive_fuzztest_macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "derive_fuzztest_macro" 3 | description = "Macro implementation for derive_fuzztest" 4 | version.workspace = true 5 | edition.workspace = true 6 | publish.workspace = true 7 | license.workspace = true 8 | categories.workspace = true 9 | repository.workspace = true 10 | 11 | [dependencies] 12 | quote.workspace = true 13 | proc-macro2.workspace = true 14 | syn = { workspace = true, features = ["extra-traits"]} 15 | 16 | [dev-dependencies] 17 | pretty_assertions.workspace = true 18 | prettyplease.workspace = true 19 | 20 | [features] 21 | quickcheck = [] 22 | proptest = [] 23 | 24 | [lib] 25 | proc-macro = true 26 | doc = false 27 | -------------------------------------------------------------------------------- /derive_fuzztest_macro/README.md: -------------------------------------------------------------------------------- 1 | derive_fuzztest_macro 2 | ========== 3 | 4 | Internal helper macro for [`derive_fuzztest`](https://crates.io/crates/derive_fuzztest). 5 | -------------------------------------------------------------------------------- /derive_fuzztest_macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Internal crate for use by [`derive_fuzztest`](../derive_fuzztest/index.html). See the 16 | //! documentation there for usage information. 17 | 18 | use proc_macro::TokenStream; 19 | use proc_macro2::{Span, TokenStream as TokenStream2}; 20 | use quote::quote; 21 | use syn::{parse::Nothing, spanned::Spanned, Ident, ItemFn, Pat, PatIdent, PatType, Type}; 22 | 23 | /// Define a fuzz test. 24 | /// 25 | /// All input parameters of the given function must implement `arbitrary::Arbitrary`. 26 | /// 27 | /// This macro derives new items based on the given function. 28 | /// 1. A `fuzz_target!` is generated that can be used with `cargo fuzz`. 29 | /// 2. Property tests (`quickcheck` or `proptest`, based on which features are enabled) are 30 | /// generated that can be tested using `cargo test`. 31 | /// 32 | /// See the crate documentation [`derive_fuzztest`](../derive_fuzztest/index.html) for details. 33 | #[proc_macro_attribute] 34 | pub fn fuzztest(attr: TokenStream, item: TokenStream) -> TokenStream { 35 | fuzztest_impl(attr.into(), item.into()) 36 | .unwrap_or_else(|e| e.into_compile_error()) 37 | .into() 38 | } 39 | 40 | fn fuzztest_impl(attr: TokenStream2, item: TokenStream2) -> syn::Result { 41 | syn::parse2::(attr)?; 42 | let func = syn::parse2::(item)?; 43 | let fn_def = FunctionDefinition::parse(func)?; 44 | let original_fn = &fn_def.func; 45 | let fuzz_target = derive_fuzz_target(&fn_def); 46 | let proptest_target = proptest::derive_proptest(&fn_def); 47 | let quickcheck_target = quickcheck::derive_quickcheck(&fn_def); 48 | 49 | Ok(quote! { 50 | #[allow(unused)] 51 | #original_fn 52 | #fuzz_target 53 | #proptest_target 54 | #quickcheck_target 55 | }) 56 | } 57 | 58 | /// Define a fuzz target only without corresponding test. 59 | /// 60 | /// All input parameters of the given function must implement `arbitrary::Arbitrary`. 61 | /// 62 | /// This macro derives a `fuzz_target!` that can be used with `cargo fuzz`. If you wish to generate 63 | /// property tests that can be used with `cargo test` as well, use [`fuzztest`][macro@fuzztest]. 64 | /// 65 | /// See the crate documentation [`derive_fuzztest`](../derive_fuzztest/index.html) for details. 66 | #[proc_macro_attribute] 67 | pub fn fuzz(attr: TokenStream, item: TokenStream) -> TokenStream { 68 | fuzz_impl(attr.into(), item.into()) 69 | .unwrap_or_else(|e| e.into_compile_error()) 70 | .into() 71 | } 72 | 73 | fn fuzz_impl(attr: TokenStream2, item: TokenStream2) -> syn::Result { 74 | syn::parse2::(attr)?; 75 | let func = syn::parse2::(item)?; 76 | let fn_def = FunctionDefinition::parse(func)?; 77 | let original_fn = &fn_def.func; 78 | let fuzz_target = derive_fuzz_target(&fn_def); 79 | 80 | Ok(quote! { 81 | #[allow(unused)] 82 | #original_fn 83 | #fuzz_target 84 | }) 85 | } 86 | 87 | /// Define a property test. 88 | /// 89 | /// This is similar to using `quickcheck!` or `proptest::proptest!` directly. 90 | /// 91 | /// All input parameters of the given function must implement `arbitrary::Arbitrary`. 92 | /// 93 | /// Unlike [`fuzztest`][macro@fuzztest], this macro does not have to be placed in a `[[bin]]` target 94 | /// and a single file can contain multiple of these tests. The generated tests can be run with 95 | /// `cargo test` as usual. 96 | #[proc_macro_attribute] 97 | pub fn proptest(attr: TokenStream, item: TokenStream) -> TokenStream { 98 | proptest_impl(attr.into(), item.into()) 99 | .unwrap_or_else(|e| e.into_compile_error()) 100 | .into() 101 | } 102 | 103 | fn proptest_impl(attr: TokenStream2, item: TokenStream2) -> syn::Result { 104 | syn::parse2::(attr)?; 105 | let func = syn::parse2::(item)?; 106 | let fn_def = FunctionDefinition::parse(func)?; 107 | let original_fn = &fn_def.func; 108 | let proptest_target = proptest::derive_proptest(&fn_def); 109 | let quickcheck_target = quickcheck::derive_quickcheck(&fn_def); 110 | 111 | Ok(quote! { 112 | #[allow(unused)] 113 | #original_fn 114 | #quickcheck_target 115 | #proptest_target 116 | }) 117 | } 118 | 119 | fn derive_fuzz_target(fn_def: &FunctionDefinition) -> proc_macro2::TokenStream { 120 | let FunctionDefinition { func, args, types } = fn_def; 121 | let func_ident = &func.sig.ident; 122 | let func_output = &func.sig.output; 123 | 124 | quote! { 125 | extern crate std; 126 | #[automatically_derived] 127 | #[cfg(fuzzing)] 128 | ::libfuzzer_sys::fuzz_target!(|args: ( #(#types),* )| #func_output { 129 | let ( #(#args),* ) = args; // https://github.com/rust-fuzz/libfuzzer/issues/77 130 | #func_ident ( #(#args),* ) 131 | }); 132 | 133 | #[cfg(not(any(fuzzing, rust_analyzer)))] 134 | fn main() { 135 | extern crate std; 136 | std::unreachable!("Run this target with `cargo fuzz` or `cargo test` instead"); 137 | } 138 | } 139 | } 140 | 141 | #[cfg(any(feature = "quickcheck", test))] 142 | mod quickcheck { 143 | use crate::FunctionDefinition; 144 | use quote::quote; 145 | 146 | pub(crate) fn derive_quickcheck(fn_def: &FunctionDefinition) -> proc_macro2::TokenStream { 147 | let FunctionDefinition { func, args, types } = fn_def; 148 | let func_ident = &func.sig.ident; 149 | let adapted_types: Vec<_> = types 150 | .iter() 151 | .map(|ty| quote! { ArbitraryAdapter<#ty> }) 152 | .collect(); 153 | let arg_pattern: Vec<_> = args 154 | .iter() 155 | .map(|arg| quote! { ArbitraryAdapter(::core::result::Result::Ok(#arg)) }) 156 | .collect(); 157 | let test_name = quote::format_ident!("quickcheck_{func_ident}"); 158 | quote! { 159 | #[automatically_derived] 160 | #[test] 161 | fn #test_name() { 162 | use ::derive_fuzztest::reexport::quickcheck::TestResult as QcTestResult; 163 | use ::derive_fuzztest::arbitrary_bridge::ArbitraryAdapter; 164 | extern crate std; 165 | 166 | fn inner(args: (#(#adapted_types),*)) -> QcTestResult 167 | { 168 | let (#(#arg_pattern),*) = args else { return QcTestResult::discard() }; 169 | match std::panic::catch_unwind(move || { 170 | #func_ident ( #(#args),* ) 171 | }) { 172 | ::core::result::Result::Ok(test_result) => QcTestResult::from(::derive_fuzztest::TestResult::from(test_result)), 173 | ::core::result::Result::Err(e) => QcTestResult::error(std::format!("{e:?}")), 174 | } 175 | } 176 | 177 | ::derive_fuzztest::reexport::quickcheck::QuickCheck::new() 178 | .tests( 179 | std::env::var("QUICKCHECK_TESTS").ok() 180 | .and_then(|val| val.parse().ok()).unwrap_or(10000) 181 | ) 182 | .quickcheck(inner as fn(_) -> QcTestResult); 183 | } 184 | } 185 | } 186 | } 187 | 188 | #[cfg(not(any(feature = "quickcheck", test)))] 189 | mod quickcheck { 190 | use crate::FunctionDefinition; 191 | 192 | pub(crate) fn derive_quickcheck(_fn_def: &FunctionDefinition) -> proc_macro2::TokenStream { 193 | proc_macro2::TokenStream::default() 194 | } 195 | } 196 | 197 | #[cfg(any(feature = "proptest", test))] 198 | mod proptest { 199 | use crate::FunctionDefinition; 200 | use quote::quote; 201 | use syn::{Ident, Signature}; 202 | 203 | pub(crate) fn derive_proptest(fn_def: &FunctionDefinition) -> proc_macro2::TokenStream { 204 | let FunctionDefinition { func, args, types } = fn_def; 205 | let func_attrs = &func.attrs; 206 | let Signature { 207 | constness, 208 | asyncness, 209 | unsafety, 210 | abi, 211 | fn_token, 212 | ident, 213 | generics, 214 | paren_token: _, 215 | inputs: _, 216 | variadic: _, 217 | output: _, 218 | } = &func.sig; 219 | let proptest_ident = Ident::new(&format!("proptest_{ident}"), ident.span()); 220 | quote! { 221 | #[automatically_derived] 222 | #[cfg(test)] 223 | mod #proptest_ident { 224 | extern crate std; 225 | use super::*; 226 | use ::derive_fuzztest::reexport::proptest; 227 | use ::derive_fuzztest::reexport::proptest_arbitrary_interop::arb; 228 | 229 | #[test] 230 | #(#func_attrs)* 231 | #constness #asyncness #unsafety #abi #fn_token #proptest_ident #generics () { 232 | let config = proptest::prelude::ProptestConfig { 233 | cases: 1024, 234 | max_global_rejects: 65536, 235 | failure_persistence: Some(std::boxed::Box::new(proptest::test_runner::FileFailurePersistence::WithSource("regression"))), 236 | test_name: Some(concat!(std::module_path!(), "::", stringify!(#proptest_ident))), 237 | source_file: Some(file!()), 238 | ..Default::default() 239 | }; 240 | let config = proptest::test_runner::contextualize_config(config); 241 | let mut runner = proptest::test_runner::TestRunner::new(config); 242 | match runner.run(&arb::<(#(#types),*)>(), |args| { 243 | let (#(#args),*) = args; 244 | proptest::test_runner::TestCaseResult::from(::derive_fuzztest::TestResult::from(#ident ( #(#args),* ))) 245 | }) { 246 | Ok(()) => (), 247 | Err(e) => panic!("{e:?}\n{runner:?}") 248 | } 249 | } 250 | } 251 | } 252 | } 253 | } 254 | 255 | #[cfg(not(any(feature = "proptest", test)))] 256 | mod proptest { 257 | use crate::FunctionDefinition; 258 | 259 | pub(crate) fn derive_proptest(_fn_def: &FunctionDefinition) -> proc_macro2::TokenStream { 260 | proc_macro2::TokenStream::default() 261 | } 262 | } 263 | 264 | /// Representation of a function definition annotated with one of the attribute macros in this 265 | /// crate. 266 | struct FunctionDefinition { 267 | func: ItemFn, 268 | args: Vec, 269 | types: Vec, 270 | } 271 | 272 | impl FunctionDefinition { 273 | pub fn parse(func: ItemFn) -> syn::Result { 274 | let (args, types) = func 275 | .sig 276 | .inputs 277 | .clone() 278 | .into_iter() 279 | .map(|arg| match arg { 280 | syn::FnArg::Receiver(arg_receiver) => Err(syn::Error::new( 281 | arg_receiver.span(), 282 | "Receiver not supported", 283 | )), 284 | syn::FnArg::Typed(PatType { 285 | attrs: _, 286 | pat, 287 | colon_token: _, 288 | ty, 289 | }) => Ok((*pat, *ty)), 290 | }) 291 | .try_fold((Vec::new(), Vec::new()), |(mut args, mut types), result| { 292 | result.map(|(arg, type_)| { 293 | match arg { 294 | Pat::Ident(PatIdent { ident, .. }) => args.push(ident), 295 | _ => args.push(Ident::new( 296 | &format!("fuzztest__arg{}", args.len()), 297 | Span::call_site(), 298 | )), 299 | }; 300 | types.push(type_); 301 | (args, types) 302 | }) 303 | })?; 304 | Ok(Self { func, args, types }) 305 | } 306 | } 307 | 308 | #[cfg(test)] 309 | mod tests { 310 | use crate::{fuzz_impl, fuzztest_impl, proptest_impl}; 311 | use quote::quote; 312 | use syn::parse_quote; 313 | 314 | /// Assert that a token stream for a `syn::File` is the same as expected. 315 | /// 316 | /// Usage is similar to `assert_eq!`: 317 | /// ```no_run 318 | /// assert_syn_file!( 319 | /// macro_impl(quote! { 320 | /// fn foobar() {} 321 | /// }), 322 | /// quote! { 323 | /// fn macro_rewritten_foobar() {} 324 | /// } 325 | /// ); 326 | /// ``` 327 | macro_rules! assert_syn_file { 328 | ($actual:expr, $expected:expr) => { 329 | let actual = syn::parse2::($actual).unwrap(); 330 | let expected: syn::File = $expected; 331 | // The token trees may not be completely equal with things like trailing commas and 332 | // extra braces around blocks. For the purposes of these tests, just trust that the 333 | // transformations `prettyplease` does won't affect functionality. We'll catch the rest 334 | // of the errors in the integration tests in `derive_fuzztest/fuzz` 335 | pretty_assertions::assert_str_eq!( 336 | prettyplease::unparse(&actual), 337 | prettyplease::unparse(&expected) 338 | ); 339 | }; 340 | } 341 | 342 | #[test] 343 | fn test_fuzztest_expansion() { 344 | assert_syn_file!( 345 | fuzztest_impl( 346 | quote! {}, 347 | quote! { 348 | fn foobar(input: &[u8]) { 349 | panic!("I am just a test") 350 | } 351 | } 352 | ) 353 | .unwrap(), 354 | parse_quote! { 355 | #[allow(unused)] 356 | fn foobar(input: &[u8]) { 357 | panic!("I am just a test") 358 | } 359 | 360 | extern crate std; 361 | #[automatically_derived] 362 | #[cfg(fuzzing)] 363 | ::libfuzzer_sys::fuzz_target!(|args: (&[u8])| { 364 | let (input) = args; 365 | foobar(input) 366 | }); 367 | 368 | #[cfg(not(any(fuzzing, rust_analyzer)))] 369 | fn main() { 370 | extern crate std; 371 | std::unreachable!("Run this target with `cargo fuzz` or `cargo test` instead"); 372 | } 373 | 374 | #[automatically_derived] 375 | #[cfg(test)] 376 | mod proptest_foobar { 377 | extern crate std; 378 | use super::*; 379 | use ::derive_fuzztest::reexport::proptest; 380 | use ::derive_fuzztest::reexport::proptest_arbitrary_interop::arb; 381 | #[test] 382 | fn proptest_foobar() { 383 | let config = proptest::prelude::ProptestConfig { 384 | cases: 1024, 385 | max_global_rejects: 65536, 386 | failure_persistence: Some( 387 | std::boxed::Box::new(proptest::test_runner::FileFailurePersistence::WithSource("regression")), 388 | ), 389 | test_name: Some( 390 | concat!(std::module_path!(), "::", stringify!(proptest_foobar)), 391 | ), 392 | source_file: Some(file!()), 393 | ..Default::default() 394 | }; 395 | let config = proptest::test_runner::contextualize_config(config); 396 | let mut runner = proptest::test_runner::TestRunner::new(config); 397 | match runner 398 | .run( 399 | &arb::<(&[u8])>(), 400 | |args| { 401 | let (input) = args; 402 | proptest::test_runner::TestCaseResult::from(::derive_fuzztest::TestResult::from(foobar(input))) 403 | }, 404 | ) 405 | { 406 | Ok(()) => {} 407 | Err(e) => panic!("{e:?}\n{runner:?}"), 408 | } 409 | } 410 | } 411 | 412 | #[automatically_derived] 413 | #[test] 414 | fn quickcheck_foobar() { 415 | use ::derive_fuzztest::reexport::quickcheck::TestResult as QcTestResult; 416 | use ::derive_fuzztest::arbitrary_bridge::ArbitraryAdapter; 417 | extern crate std; 418 | 419 | fn inner(args: (ArbitraryAdapter<&[u8]>)) -> QcTestResult { 420 | let (ArbitraryAdapter(::core::result::Result::Ok(input))) = args else { 421 | return QcTestResult::discard() 422 | }; 423 | match std::panic::catch_unwind(move || { foobar(input) }) { 424 | ::core::result::Result::Ok(test_result) => QcTestResult::from(::derive_fuzztest::TestResult::from(test_result)), 425 | ::core::result::Result::Err(e) => QcTestResult::error(std::format!("{e:?}")), 426 | } 427 | } 428 | ::derive_fuzztest::reexport::quickcheck::QuickCheck::new() 429 | .tests( 430 | std::env::var("QUICKCHECK_TESTS").ok() 431 | .and_then(|val| val.parse().ok()).unwrap_or(10000) 432 | ) 433 | .quickcheck(inner as fn(_) -> QcTestResult); 434 | } 435 | } 436 | ); 437 | } 438 | 439 | #[test] 440 | fn test_fuzz_expansion() { 441 | assert_syn_file!( 442 | fuzz_impl( 443 | quote! {}, 444 | quote! { 445 | fn foobar(input: &[u8]) { 446 | panic!("I am just a test") 447 | } 448 | } 449 | ) 450 | .unwrap(), 451 | parse_quote! { 452 | #[allow(unused)] 453 | fn foobar(input: &[u8]) { 454 | panic!("I am just a test") 455 | } 456 | 457 | extern crate std; 458 | #[automatically_derived] 459 | #[cfg(fuzzing)] 460 | ::libfuzzer_sys::fuzz_target!(|args: (&[u8])| { 461 | let (input) = args; 462 | foobar(input) 463 | }); 464 | 465 | #[cfg(not(any(fuzzing, rust_analyzer)))] 466 | fn main() { 467 | extern crate std; 468 | std::unreachable!("Run this target with `cargo fuzz` or `cargo test` instead"); 469 | } 470 | } 471 | ); 472 | } 473 | 474 | #[test] 475 | fn test_proptest_expansion() { 476 | assert_syn_file!( 477 | proptest_impl( 478 | quote! {}, 479 | quote! { 480 | fn foobar(input: &[u8]) { 481 | panic!("I am just a test") 482 | } 483 | } 484 | ) 485 | .unwrap(), 486 | parse_quote! { 487 | #[allow(unused)] 488 | fn foobar(input: &[u8]) { 489 | panic!("I am just a test") 490 | } 491 | 492 | #[automatically_derived] 493 | #[test] 494 | fn quickcheck_foobar() { 495 | use ::derive_fuzztest::reexport::quickcheck::TestResult as QcTestResult; 496 | use ::derive_fuzztest::arbitrary_bridge::ArbitraryAdapter; 497 | extern crate std; 498 | 499 | fn inner(args: (ArbitraryAdapter<&[u8]>)) -> QcTestResult { 500 | let (ArbitraryAdapter(::core::result::Result::Ok(input))) = args else { 501 | return QcTestResult::discard() 502 | }; 503 | match std::panic::catch_unwind(move || { foobar(input) }) { 504 | ::core::result::Result::Ok(test_result) => QcTestResult::from(::derive_fuzztest::TestResult::from(test_result)), 505 | ::core::result::Result::Err(e) => QcTestResult::error(std::format!("{e:?}")), 506 | } 507 | } 508 | ::derive_fuzztest::reexport::quickcheck::QuickCheck::new() 509 | .tests( 510 | std::env::var("QUICKCHECK_TESTS").ok() 511 | .and_then(|val| val.parse().ok()).unwrap_or(10000) 512 | ) 513 | .quickcheck(inner as fn(_) -> QcTestResult); 514 | } 515 | 516 | #[automatically_derived] 517 | #[cfg(test)] 518 | mod proptest_foobar { 519 | extern crate std; 520 | use super::*; 521 | use ::derive_fuzztest::reexport::proptest; 522 | use ::derive_fuzztest::reexport::proptest_arbitrary_interop::arb; 523 | #[test] 524 | fn proptest_foobar() { 525 | let config = proptest::prelude::ProptestConfig { 526 | cases: 1024, 527 | max_global_rejects: 65536, 528 | failure_persistence: Some( 529 | std::boxed::Box::new( 530 | proptest::test_runner::FileFailurePersistence::WithSource("regression"), 531 | ), 532 | ), 533 | test_name: Some( 534 | concat!(std::module_path!(), "::", stringify!(proptest_foobar)), 535 | ), 536 | source_file: Some(file!()), 537 | ..Default::default() 538 | }; 539 | let config = proptest::test_runner::contextualize_config(config); 540 | let mut runner = proptest::test_runner::TestRunner::new(config); 541 | match runner 542 | .run( 543 | &arb::<(&[u8])>(), 544 | |args| { 545 | let (input) = args; 546 | proptest::test_runner::TestCaseResult::from(::derive_fuzztest::TestResult::from(foobar(input))) 547 | }, 548 | ) 549 | { 550 | Ok(()) => {} 551 | Err(e) => panic!("{e:?}\n{runner:?}"), 552 | } 553 | } 554 | } 555 | } 556 | ); 557 | } 558 | } 559 | --------------------------------------------------------------------------------