My experience (real world, for actually existing code) is that property tests often require a lot of fiddling with the data generation, in order to actually stress your system in interesting ways. If you just throw "totally random" data into your system, you won't be testing very interesting properties. Amazing assurances and payoff from doing it, of course! Just like... "just generate random arbitraries" works a lot less when you're working with 100 field structs and only 10 or so "matter" in senses you care about.
To my knowledge I have not seen a property testing toolkit that leverages code coverage in the way fuzzing does.
They turned it off (not sure if they turned it on again): the problem is that property based tests of the kind implemented with hypothesis are mainly supposed to be run as part of your CI/CD test suit. So they should be fast.
Coverage guided fuzzing like AFL does usually takes longer than people are prepared to wait for in their build/test server.
You run the tests briefly in CI to check for regressions, and then leave them running permanently on a fuzz server to search for new bugs. Nelson Elhage has a good writeup of this approach at https://blog.nelhage.com/post/two-kinds-of-testing/
That said, for me: they are distinct but related, and that distinction is useful.
For example, Hypothesis[1] is a popular property testing framework. The authors have more recently created HypoFuzz[2], which includes this sentence in the introduction:
“HypoFuzz runs your property-based test suite, using cutting-edge fuzzing techniques and coverage instrumentation to find even the rarest inputs which trigger an error.”
Being able to talk about fuzzing and property testing as distinct things seems useful — saying something like “We added fuzzing techniques to our property testing framework” is more meaningful than “We added property testing techniques to our property testing framework” ;-)
My personal hope is there will be more convergence, and work to add convenient first-class fuzzing support in a popular language like Go will hopefully help move the primary use case for fuzzing to be about correctness, with security moving to an important but secondary use case.
In Rust the driver for property and fuzz testing can be shared, which is nice[0].
https://docs.rs/arbitrary/1.0.1/arbitrary/trait.Arbitrary.ht...
By describing my data with arbitrary I've written programs that had traditional property testing and fuzz-testing as well, without any additional effort.
It's only really post-AFL where fuzzing has suddenly become synonymous with instrumentation guided data generation, which is a totally fine distinction to make, but they're still fundamentally equivalent. The first fuzzers were virtually identical to prop test frameworks today.
I'd be fine with dropping the "fuzzing" name entirely and instead just having us use the term property testing, with "data generation" being the thing we start differentiating ie: "random prop testing" or "instrumentation guided prop testing" or "type based prop testing" etc.
Property testing generates these bits using a specified distribution, and that's about it. Fuzz testing generates these bits by looking at how the program is executed, and uses a black box to try to explore all paths in the program.
Most libraries for property testing comes with very convenient ways to craft the "input to data" part. Fuzz tools come with an almost magically effective way to craft interesting inputs. The two combines very well (and have been combined in several libraries).
This is why you can use one of the approaches to help the other side of the approach.
The 3rd solution is concolic testing: use an SMT/SAT solver to flip branches. The path down to the branch imposes a formula. By inverting the formula, we can pick a certain branch path. Now you ask the SMT solver to check there there's no way to go down that branch. If it finds satisfiability, you have a counterexample and can explore that path.
Coverage guided fuzzing is eating symbolic execution and concolic testing for breakfast. It isn't even close. As much as I love these more principled approaches, the strategy of throwing bits at programs and applying careful coverage metrics is just way more effective, even for cases that seem to be hand picked for SMT-based approaches to win.
I think most property testing frameworks also come with the concept of "shrinkage", which is a way to walk back a failed condition to try to find the "minimum requirement of failure". Though I am sure there are PT frameworks that haven't implemented this.
Of all the property based testing libraries, hypothesis has some great ideas on shrinking. (By comparison, Haskell's QuickCheck is actually pretty bad at shrinking.)
This here is closer to what I see as property testing than fuzzing, although it looks like they plan on coverage feedback (so guided generation).
That property can be "does not crash".
And I'd say this is the structured / language-awareness part: with fuzzing you can't generally build an oracle.
And if you can it's of course trivial: just have a wrapper script check the result against the oracle, and trigger whatever the fuzzer looks for indicating "failure" whether it's a return code or a segfault or…