testing: add hypothesis fuzz testing
Hypothesis a library for adding fuzzing over a range of structure
data to your test suite: http://hypothesis.readthedocs.org/en/latest/
This adds the ability to build tests using Hypothesis within the Mercurial test
suite. New tests and fixes using this helpers comes in later changesets.
--- a/tests/hghave.py Mon Nov 02 13:00:45 2015 +0000
+++ b/tests/hghave.py Sat Oct 24 12:46:03 2015 +0100
@@ -463,3 +463,12 @@
@check("slow", "allow slow tests")
def has_slow():
return os.environ.get('HGTEST_SLOW') == 'slow'
+
+@check("hypothesis", "is Hypothesis installed")
+def has_hypothesis():
+ try:
+ import hypothesis
+ hypothesis.given
+ return True
+ except ImportError:
+ return False
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/hypothesishelpers.py Sat Oct 24 12:46:03 2015 +0100
@@ -0,0 +1,62 @@
+# Helper module to use the Hypothesis tool in tests
+#
+# Copyright 2015 David R. MacIver
+#
+# For details see http://hypothesis.readthedocs.org
+
+import os
+import sys
+import traceback
+
+from hypothesis.settings import set_hypothesis_home_dir
+import hypothesis.strategies as st
+from hypothesis import given, Settings
+
+# hypothesis store data regarding generate example and code
+set_hypothesis_home_dir(os.path.join(
+ os.getenv('TESTTMP'), ".hypothesis"
+))
+
+def check(*args, **kwargs):
+ """decorator to make a function a hypothesis test
+
+ Decorated function are run immediately (to be used doctest style)"""
+ def accept(f):
+ # Workaround for https://github.com/DRMacIver/hypothesis/issues/206
+ # Fixed in version 1.13 (released 2015 october 29th)
+ f.__module__ = '__anon__'
+ try:
+ given(*args, settings=Settings(max_examples=2000), **kwargs)(f)()
+ except Exception:
+ traceback.print_exc(file=sys.stdout)
+ sys.exit(1)
+ return accept
+
+
+def roundtrips(data, decode, encode):
+ """helper to tests function that must do proper encode/decode roundtripping
+ """
+ @given(data)
+ def testroundtrips(value):
+ encoded = encode(value)
+ decoded = decode(encoded)
+ if decoded != value:
+ raise ValueError(
+ "Round trip failed: %s(%r) -> %s(%r) -> %r" % (
+ encode.__name__, value, decode.__name__, encoded,
+ decoded
+ ))
+ try:
+ testroundtrips()
+ except Exception:
+ # heredoc swallow traceback, we work around it
+ traceback.print_exc(file=sys.stdout)
+ raise
+ print("Round trip OK")
+
+
+# strategy for generating bytestring that might be an issue for Mercurial
+bytestrings = (
+ st.builds(lambda s, e: s.encode(e), st.text(), st.sampled_from([
+ 'utf-8', 'utf-16',
+ ]))) | st.binary()