From 26b3dcd291df303720f69aca87a4a60569f02064 Mon Sep 17 00:00:00 2001 From: Gab_km Date: Sat, 25 Oct 2014 16:34:07 +0900 Subject: [PATCH] Add `for_all` function as a substitute for `Arbitrary` and `Arbitrary#property` --- lib/pyqcheck/__init__.py | 2 +- lib/pyqcheck/prop.py | 20 ++++++++++++ lib/test/_import.py | 2 +- lib/test/case_for_all.py | 66 +++++++++++++++++++++++++++++++++++++++ lib/test/case_pyqcheck.py | 36 ++++++++++++++++++++- 5 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 lib/test/case_for_all.py diff --git a/lib/pyqcheck/__init__.py b/lib/pyqcheck/__init__.py index 0b6aae9..d993caf 100644 --- a/lib/pyqcheck/__init__.py +++ b/lib/pyqcheck/__init__.py @@ -30,7 +30,7 @@ from multiprocessing import Process -from prop import Prop, PropRunner +from prop import Prop, PropRunner, for_all, may_throw from arbitrary import Arbitrary, ArbitraryAbstraction from pyqworker import PyQWorker from util import PrettyPrinter diff --git a/lib/pyqcheck/prop.py b/lib/pyqcheck/prop.py index 6fee2fe..184c94b 100644 --- a/lib/pyqcheck/prop.py +++ b/lib/pyqcheck/prop.py @@ -4,6 +4,7 @@ import sys import traceback import marshal +from arbitrary import Arbitrary class PropResult: @@ -53,6 +54,25 @@ def execute(self): return PropResult.fail(func.__name__, inputs, error) +def for_all(arbitraries, label, func): + arb = Arbitrary(*arbitraries) + if type(func) == PossibleFuncToThrow: + return Prop(arb, func.to_function, label, *(func.exceptions)) + else: + return Prop(arb, func, label) + + +class PossibleFuncToThrow: + def __init__(self, func, should_all_throw, *exception): + self.to_function = func + self.should_all_throw = should_all_throw + self.exceptions = exception + + +def may_throw(func, *exception): + return PossibleFuncToThrow(func, False, *exception) + + class RunningResult: def __init__(self, label, func_name, func_code, success, failure, exceptions, results): self.label = label diff --git a/lib/test/_import.py b/lib/test/_import.py index 0a9a67d..97a6011 100644 --- a/lib/test/_import.py +++ b/lib/test/_import.py @@ -8,7 +8,7 @@ if INCLUDE_PATH not in sys.path: sys.path.insert(0, INCLUDE_PATH) -from pyqcheck import PyQCheck, Arbitrary, ArbitraryAbstraction, set_arbitrary, Prop, PropRunner, PrettyPrinter +from pyqcheck import PyQCheck, Arbitrary, ArbitraryAbstraction, set_arbitrary, Prop, for_all, may_throw, PropRunner, PrettyPrinter from pyqcheck.arbitraries.pq_string import PyQString from pyqcheck.arbitraries.pq_integer import PyQInteger from pyqcheck.arbitraries.pq_number import PyQNumber diff --git a/lib/test/case_for_all.py b/lib/test/case_for_all.py new file mode 100644 index 0000000..6651a25 --- /dev/null +++ b/lib/test/case_for_all.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- + +from _import import PyQCheck, Arbitrary, for_all, may_throw, PropRunner + + +describe "for_all test": + + before each: + PyQCheck.TEST_STACK = [] + + it "should succeed all tests when for_all creates an all true property": + test_label = '!(x || y) == !x && !y' + test_func = lambda x, y: (not(x or y)) == ((not x) and (not y)) + + result = ( + PropRunner(1000).run( + for_all( + ('boolean', 'boolean'), + test_label, + test_func + ) + ).test_result + ) + + assert result.label == test_label + assert result.success == 1000 + assert result.failure == 0 + + it "should succeed all tests when for_all creates an all true property with limitations": + test_label = 'x + y == y + x' + test_func = lambda x, y: x + y == y + x + + result = ( + PropRunner(1000).run( + for_all( + (('number', {"min":5, "max":10}), ('number', {"min":5, "max":10})), + test_label, + test_func + ) + ).test_result + ) + + assert result.label == test_label + assert result.success == 1000 + assert result.failure == 0 + + it "should catch errors when for_all creates a fragile property": + test_label = 'n <= 10 == True' + test_number = 1000 + def ten_or_less(n): + if n > 10: + raise ValueError + return True + + result = ( + PropRunner(test_number).run( + for_all( + (('integer', {"max":30}),), + test_label, + may_throw(ten_or_less, ValueError) + ) + ).test_result + ) + + assert result.label == test_label + assert result.success == test_number or not (result.exceptions.get("ValueError") is None) diff --git a/lib/test/case_pyqcheck.py b/lib/test/case_pyqcheck.py index ebdabcd..630d949 100644 --- a/lib/test/case_pyqcheck.py +++ b/lib/test/case_pyqcheck.py @@ -1,6 +1,6 @@ # -*- coding:utf-8 -*- -from _import import PyQCheck, Arbitrary, set_arbitrary +from _import import PyQCheck, Arbitrary, set_arbitrary, for_all from decimal import Decimal, getcontext getcontext().prec = 60 @@ -122,3 +122,37 @@ def length_10(chars): assert test_3.label == 'len(chars) <= 10' assert test_3.success == TEST_COUNT assert test_3.failure == 0 + + it "can register 2 tests and should succeed when it runs them": + TEST_COUNT = 100 + TEST_LABEL_1 = '!(x || y) == !x && !y' + TEST_LABEL_2 = 'x * y == y * x and x + y == y + x' + + def eq(x,y): + return x * y == y * x and x + y == y + x + + results = PyQCheck().add( + for_all( + ('boolean', 'boolean'), + TEST_LABEL_1, + lambda x, y: (not(x or y)) == ((not x) and (not y)) + ) + ).add( + for_all( + ('integer', 'integer'), + TEST_LABEL_2, + eq + ) + ).run(TEST_COUNT).results + + assert len(results) == 2 + + result_1 = results[0] + result_2 = results[1] + + assert result_1.label == TEST_LABEL_1 + assert result_1.success == TEST_COUNT + assert result_1.failure == 0 + assert result_2.label == TEST_LABEL_2 + assert result_2.success == TEST_COUNT + assert result_2.failure == 0