├── .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 | [](https://crates.io/crates/derive_fuzztest) [](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 |
--------------------------------------------------------------------------------