Merge pull request #81 from MaxG87/master

Add property-based fuzz test
diff --git a/.gitignore b/.gitignore
index 413f6e2..69504d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 *.egg
 *.egg-info
 *.py[cod]
+.hypothesis/
 .tox
 dist
diff --git a/test_mccabe.py b/test_mccabe.py
index b53db6a..fe6e8d3 100644
--- a/test_mccabe.py
+++ b/test_mccabe.py
@@ -1,11 +1,17 @@
 import unittest
 import sys
+
 try:
     from StringIO import StringIO
 except ImportError:
     from io import StringIO
 
 import pytest
+try:
+    import hypothesmith
+    from hypothesis import HealthCheck, given, settings, strategies as st
+except ImportError:
+    hypothesmith = None
 
 import mccabe
 from mccabe import get_code_complexity
@@ -233,5 +239,41 @@
         self.assertEqual(0, mccabe.get_module_complexity("mccabe.py"))
 
 
+# This test uses the Hypothesis and Hypothesmith libraries to generate random
+# syntatically-valid Python source code and applies McCabe on it.
+@settings(
+    max_examples=1000,  # roughly 1k tests/minute, or half that under coverage
+    derandomize=False,  # deterministic mode to avoid CI flakiness
+    deadline=None,  # ignore Hypothesis' health checks; we already know that
+    suppress_health_check=HealthCheck.all(),  # this is slow and filter-heavy.
+)
+@given(
+    # Note that while Hypothesmith might generate code unlike that written by
+    # humans, it's a general test that should pass for any *valid* source code.
+    # (so e.g. running it against code scraped of the internet might also help)
+    src_contents=hypothesmith.from_grammar() | hypothesmith.from_node(),
+    max_complexity=st.integers(min_value=1),
+)
+@pytest.mark.skipif(not hypothesmith, reason="hypothesmith could not be imported")
+def test_idempotent_any_syntatically_valid_python(
+    src_contents: str, max_complexity: int
+) -> None:
+    """Property-based tests for mccabe.
+
+    This test case is based on a similar test for Black, the code formatter.
+    Black's test was written by Zac Hatfield-Dodds, the author of Hypothesis
+    and the Hypothesmith tool for source code generation.  You can run this
+    file with `python`, `pytest`, or (soon) a coverage-guided fuzzer Zac is
+    working on.
+    """
+
+    # Before starting, let's confirm that the input string is valid Python:
+    compile(src_contents, "<string>", "exec")  # else bug is in hypothesmith
+
+    # Then try to apply get_complexity_number to the code...
+    get_code_complexity(src_contents, max_complexity)
+
+
 if __name__ == "__main__":
+    test_idempotent_any_syntatically_valid_python()
     unittest.main()
diff --git a/tox.ini b/tox.ini
index 6337825..5965f63 100644
--- a/tox.ini
+++ b/tox.ini
@@ -5,6 +5,8 @@
 [testenv]
 deps =
     pytest
+    hypothesis ; python_version >= "3.6"
+    hypothesmith ; python_version >= "3.6"
 commands =
     pytest