Skip to content

Commit e46bf28

Browse files
committed
tweak ext-fuzzer introduction
1 parent 185dba4 commit e46bf28

2 files changed

Lines changed: 28 additions & 30 deletions

File tree

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
Use an external fuzzer with Hypothesis
1+
Use Hypothesis with an external fuzzer
22
======================================
33

4-
.. seealso::
4+
We think property-based testing is great, but sometimes you might want to point a traditional fuzzer at your code, such as `python-afl <https://github.com/jwilk/python-afl>`__ or Google's :pypi:`atheris` (which instruments both Python and native extensions).
55

6-
If you're looking to fuzz property-based tests, `HypoFuzz <https://hypofuzz.com/>`_ is a coverage-guided fuzzer built for Hypothesis.
6+
You might also want to use Hypothesis strategies to describe your input data, and our world-class shrinking and observability tools to wrangle the results. That's exactly what this how-to guide is about!
77

8-
In a standard Hypothesis test run, Hypothesis is responsible for generating each test case. However, you might instead want to point a traditional fuzzer at your code, such as `python-afl <https://github.com/jwilk/python-afl>`__ or Google's :pypi:`atheris` (which instruments both Python and native extensions).
8+
.. note::
9+
10+
This page is about writing traditional 'fuzz harnesses' for an external fuzzer, using parts of Hypothesis.
11+
If you already have Hypothesis tests and want to fuzz them, we strongly recommend the purpose-built `HypoFuzz <https://hypofuzz.com/>`_.
912

1013
In order to support this workflow, Hypothesis exposes the |fuzz_one_input| method. |fuzz_one_input| takes a bytestring, parses it into a test case, and executes the corresponding test once. This means you can treat each of your Hypothesis tests as a traditional fuzz target, by pointing the fuzzer at |fuzz_one_input|.
1114

@@ -27,10 +30,10 @@ Note that |fuzz_one_input| bypasses the standard test lifecycle. In a standard t
2730

2831
See the documentation of |fuzz_one_input| for details of how it interacts with other features of Hypothesis, such as |@settings|.
2932

30-
Using Atheris with |fuzz_one_input|
31-
-----------------------------------
33+
Worked example: using Atheris
34+
-----------------------------
3235

33-
Here is an example that uses the `Atheris <https://github.com/google/atheris>`__ coverage-guided fuzzer (which is built on top of `libFuzzer <https://llvm.org/docs/LibFuzzer.html>`_) with |fuzz_one_input|:
36+
Here is an example that uses |fuzz_one_input| with the `Atheris <https://github.com/google/atheris>`__ coverage-guided fuzzer (which is built on top of `libFuzzer <https://llvm.org/docs/LibFuzzer.html>`_):
3437

3538
.. code-block:: python
3639
@@ -41,28 +44,18 @@ Here is an example that uses the `Atheris <https://github.com/google/atheris>`__
4144
4245
from hypothesis import given, strategies as st
4346
44-
json_strategy = st.deferred(lambda: st.none() | st.floats() | st.text() | lists)
45-
lists = st.lists(json_strategy)
46-
47-
@given(json_strategy)
48-
def test_json_dums_valid_json(value):
47+
@given(
48+
st.recursive(
49+
st.none() | st.booleans() | st.integers() | st.floats() | st.text(),
50+
lambda j: st.lists(j) | st.dictionaries(st.text(), j)
51+
)
52+
)
53+
def test_json_dumps_valid_json(value):
4954
json.dumps(value)
5055
51-
atheris.Setup(sys.argv, test_json_dums_valid_json.hypothesis.fuzz_one_input)
56+
atheris.Setup(sys.argv, test_json_dumps_valid_json.hypothesis.fuzz_one_input)
5257
atheris.Fuzz()
5358
54-
You may also want to use ``atheris.instrument_all`` or ``atheris.instrument_imports`` in order to add coverage instrumentation to Atheris. For example, to instrument the ``json`` module for coverage:
55-
56-
57-
.. code-block:: python
58-
59-
...
60-
61-
import atheris
62-
63-
with atheris.instrument_imports():
64-
import json # fmt: off
65-
66-
...
59+
Generating valid JSON objects based only on Atheris' ``FuzzDataProvider`` interface would be considerably more difficult.
6760

68-
See the `Atheris <https://github.com/google/atheris>`__ documentation for full details.
61+
You may also want to use ``atheris.instrument_all`` or ``atheris.instrument_imports`` in order to add coverage instrumentation to Atheris. See the `Atheris <https://github.com/google/atheris>`__ documentation for full details.

hypothesis-python/src/hypothesis/core.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1649,9 +1649,6 @@ def fuzz_one_input(
16491649
"""
16501650
Run the test as a fuzz target, driven with the ``buffer`` of bytes.
16511651
1652-
Returns None if ``buffer`` was invalid for the strategy, canonical pruned
1653-
bytes if the buffer was valid, and leaves raised exceptions alone.
1654-
16551652
Depending on the passed ``buffer`` one of three things will happen:
16561653
16571654
* If the bytestring was invalid, for example because it was too short or was
@@ -1666,6 +1663,14 @@ def fuzz_one_input(
16661663
minimize, and de-duplicate all the failures found via fuzzing is run
16671664
your test suite!
16681665
1666+
To reduce the performance impact of database writes, |fuzz_one_input| only
1667+
records failing inputs which would be valid shrinks for a known failure -
1668+
meaning writes are somewhere between constant and log(N) rather than linear
1669+
in runtime. However, this tracking only works within a persistent fuzzing
1670+
process; for forkserver fuzzers we recommend ``database=None`` for the main
1671+
run, and then replaying with a database enabled if you need to analyse
1672+
failures.
1673+
16691674
Note that the interpretation of both input and output bytestrings is
16701675
specific to the exact version of Hypothesis you are using and the strategies
16711676
given to the test, just like the :ref:`database <database>` and

0 commit comments

Comments
 (0)