import itertools
import sys
import six
from .arbitraries import Arbitrary
__all__ = ['for_all', 'Result', 'ReturnedFalse', 'CheckError']
[docs]class CheckError(Exception):
""" Raised when a test which uses :func:`.qc` decorator fails.
It contains the original test data for which the test has failed, and
the exception raised by the tested function.
Actually, a :exc:`CheckError` instance is always also an instance of the
exception raised by the tested function, so you can except it as usually.
"""
#: Data which caused the function to raise an exception
test_data = None
#: Exception raised by the function, or :exc:`ReturnedFalse` if function
#: returned ``False``
cause = None
def get_CheckError(data, cause):
class CheckError_(CheckError, type(cause)):
def __init__(self, data, cause):
self.test_data = data
self.cause = cause
def __str__(self):
msg = "test failed"
if self.cause is not None:
msg += " due to %s" % type(self.cause).__name__
cause = str(self.cause)
if cause:
msg += ": " + cause
res = [msg]
res.append("Failure encountered for data:")
res.extend([" %s = %s" % i for i in sorted(self.test_data.items())])
return '\n'.join(res)
CheckError_.__name__ = 'CheckError'
return CheckError_(data, cause)
[docs]class ReturnedFalse(Exception):
""" Automatically raised when the tested function returns a value which
evaluates to a :mod:`bool` ``False``.
"""
def __init__(self, retval):
#: Exact value returned by the function (evaluates to a ``False`` :mod:`bool` value)
self.retval = retval
[docs]class Result(object):
""" Represent the result of performed testing (returned by :func:`for_all`).
"""
def __init__(self, verdict, passed, data=None, exception=None, traceback=None):
#: ``'OK'`` if passed and ``'FAIL'`` if not
self.verdict = verdict
#: ``True`` if passed and ``False`` if not (opposite to :attr:`failed`)
self.passed = passed
#: ``False`` if passed and ``True`` if not (opposite to :attr:`passed`)
self.failed = not passed
#: Data on which the tested function failed (as a :mod:`dict`),
#: or ``None`` if passed
self.data = data
#: Exception raised by the function, or :exc:`ReturnedFalse` if function
#: returned ``False``, or ``None`` if passed
self.exception = exception
#: Exception traceback, or ``None`` if passed
self.traceback = traceback
@classmethod
def ok(cls):
return Result('OK', True)
@classmethod
def fail(cls, data, exception, traceback):
return Result('FAIL', False, data, exception, traceback)
def reraise(self):
exception = get_CheckError(self.data, self.exception)
six.reraise(type(exception), exception, self.traceback)
[docs] def __bool__(self):
""" Return :attr:`passed`, used to interprete as a :mod:`bool` value.
"""
return self.passed
__nonzero__ = __bool__
class Checker(object):
def __init__(self, func, args, ntests):
self.func = func
self.args = args
self.ntests = ntests
self.arg_arbs = {}
for argname, value in self.args.items():
if isinstance(value, type) and not issubclass(value, Arbitrary):
# e.g. 'int', 'list' - any class with defined arbitrary_for
value = Arbitrary.get_for(value)
# Arbitrary instanciated above
self.arg_arbs[argname] = value
def generate_data(self):
each_amount = int(self.ntests ** (1.0 / len(self.arg_arbs)))
serial_data = [v.gen_serial(each_amount) for k, v in self.arg_arbs.items()]
for values in itertools.product(*serial_data):
kwargs = dict(zip(self.arg_arbs.keys(), values))
yield kwargs
for _ in range(self.ntests):
kwargs = {k: v.next_random() for k, v in self.arg_arbs.items()}
yield kwargs
def eval_func(self, data):
func_res = self.func(**data)
if func_res is False:
raise ReturnedFalse(func_res)
def full_shrink(self, data):
changed = True
while changed:
changed = False
shr_gens = [self.arg_arbs[k].shrink(data[k]) for k in self.arg_arbs.keys()]
for shr_values in itertools.product(*shr_gens):
shr_data = dict(zip(self.arg_arbs.keys(), shr_values))
try:
self.eval_func(shr_data)
except Exception as e:
data = shr_data
changed = True
return data
def for_all(self):
for kwargs in self.generate_data():
try:
self.eval_func(kwargs)
except Exception as e:
kwargs = self.full_shrink(kwargs)
try:
self.eval_func(kwargs)
except Exception as e:
_, _, traceback = sys.exc_info()
return Result.fail(kwargs, e, traceback)
return Result.ok()
[docs]def for_all(func, _ntests=100, **kwargs):
""" Test the function ``func`` on ``_ntests`` tests.
For usage examples see :ref:`property-checking`.
:param _ntests: number of tests
:param kwargs: function arguments specification
:returns: information about the tests run
:rtype: :class:`Result`
"""
return Checker(func, kwargs, _ntests).for_all()