Skip to content

Commit

Permalink
Fixed regression that wouldn't short-circuit boolean expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
dfunckt committed Dec 5, 2015
1 parent 0c935b9 commit 08aa03d
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 2 deletions.
6 changes: 4 additions & 2 deletions README.rst
Expand Up @@ -195,9 +195,11 @@ function would be. Predicates, however, can be combined using binary operators
to create more complex ones. Predicates support the following operators:

* ``P1 & P2``: Returns a new predicate that returns ``True`` if *both*
predicates return ``True``, otherwise ``False``.
predicates return ``True``, otherwise ``False``. If P1 returns ``False``,
P2 will not be evaluated.
* ``P1 | P2``: Returns a new predicate that returns ``True`` if *any* of the
predicates returns ``True``, otherwise ``False``.
predicates returns ``True``, otherwise ``False``. If P1 returns ``True``,
P2 will not be evaluated.
* ``P1 ^ P2``: Returns a new predicate that returns ``True`` if one of the
predicates returns ``True`` and the other returns ``False``, otherwise
``False``.
Expand Down
6 changes: 6 additions & 0 deletions rules/predicates.py
Expand Up @@ -185,6 +185,12 @@ def _combine(self, other, op, args):
if self_result is None:
return bool(other._apply(*args))

# short-circuit evaluation
if op is operator.and_ and not self_result:
return False
elif op is operator.or_ and self_result:
return True

other_result = other._apply(*args)
if other_result is None:
return self_result
Expand Down
25 changes: 25 additions & 0 deletions tests/testsuite/test_predicates.py
Expand Up @@ -292,6 +292,31 @@ def p(a, b=None):
p.test('a', NO_VALUE)


def test_short_circuit():
@predicate
def skipped_predicate(self):
return None

@predicate
def shorted_predicate(self):
raise Exception('this predicate should not be evaluated')

assert (always_false & shorted_predicate).test() is False
assert (always_true | shorted_predicate).test() is True

def raises(pred):
try:
pred.test()
return False
except Exception as e:
return 'evaluated' in str(e)

assert raises(always_true & shorted_predicate)
assert raises(always_false | shorted_predicate)
assert raises(skipped_predicate & shorted_predicate)
assert raises(skipped_predicate | shorted_predicate)


def test_skip_predicate_deprecation():
@predicate(bind=True)
def skipped_predicate(self):
Expand Down

0 comments on commit 08aa03d

Please sign in to comment.