Predicates

A predicate is a simple truth checker object inheriting from Predicate.

The following VoD gives a quick introduction to predicate-based testing.

When a Predicate is tested for truth, a user-defined checking behavior is automatically invoked. Thus, complex testing may be wrapped up into a simple truth test.

Let’s illustrate the concept of a Predicate by creating a new predicate class that accepts a number when it is constructed. The predicate only tests True when that number is even:

from genie.predcore import Predicate

class IsEvenPredicate(Predicate):
    ''' Predicate that tests True if the input value is even '''

    def __init__(self, value):
        self._value = value

    def dump(self):
        return "IsEvenPredicate({})".format(self._value)

    def test(self):
        return (self._value % 2 == 0)

numsToList = [1, 6, 8, 9]
for num in numsToList:
    pred = IsEvenPredicate(num)
    print ("          Testing {}".format(pred.dump()))
    if pred :
        print ("{} is even.".format(num))

IsEvenPredicate(8).assert_test()
IsEvenPredicate(9).assert_test()

IsEvenPredicate(8)()
IsEvenPredicate(9)()

This example produces the following output (notice the assert_test silently passes when the number being tested is even but raises a signal when the number is odd):

          Testing IsEvenPredicate(1)
          Testing IsEvenPredicate(6)
6 is even.
          Testing IsEvenPredicate(8)
8 is even.
          Testing IsEvenPredicate(9)


(signal gets raised)
genie.predcore.PredicateTestedFalseSignal: IsEvenPredicate(9)

True
False

A Predicate is a class that has the following properties:

  1. It has a test method that executes a test and returns True or False. This method is overloaded and typically runs CLI commands on a device, determining the truth value to return based on the device’s response.

  2. Whenever an object inheriting from Predicate is tested for truth (for example, in an if statement), its test method is automatically called.

  3. It has an assert_test method that is used to ensure a predicate tests True (if it doesn’t, a signal is raised).

  4. It has an optional dump method that, if overloaded, provides a powerful way to pass relevant information when the predicate does not test to the expected value.

  5. It can be called in order to perform its truth test, returning either True or False. This makes Predicate interoperable with commands such as threading.Condition.wait_for or asyncio.wait_for.

Let’s see a more device-centric example. Assume we have an InterfaceUpPredicate class that has its test method overloaded to run a show interface command on a device, parse the result, and determine if the requested interface is operationally and administratively up:

if_name = 'GigabitEthernet0/0'

check_pred = InterfaceUpPredicate(
                device         = my_device,
                interface_name = if_name)

if check_pred:
    print("Interface is up.")
else:
    print("Interface is down.")

Predicates that contain other Predicates

Let’s say that we want to validate that a series of interfaces are up by doing only a single truth test.

This can be done using AndPredicate, which is a predicate that accepts a series of user-defined objects that are able to be tested for truth. These objects don’t have to be Predicates, but in this example they will be.

Every time this predicate is tested for truth, all contained objects are scheduled for re-testing. If any object in the series tests False, then the predicate itself tests False and the remaining objects remain un-tested.

Let’s assume that InterfaceUpPredicate can accept an interface object that knows its attached device and interface name:

all_interfaces_up_pred = AndPredicate(
        InterfaceUpPredicate (interface_object = interface_1),
        InterfaceUpPredicate (interface_object = interface_2),
        InterfaceUpPredicate (interface_object = interface_3),
    )

if all_interfaces_up_pred:
    print ("All interfaces are up.")
else:
    print ("Not all interfaces are up.")

Timed Looping Predicates

Predicates that periodically test a series of objects for truth are particularly useful when doing initial test setup. predcore provides several such pre-requisite classes.

Prerequisite

When a Prerequisite object is tested for truth, it periodically checks a series of user-defined contained objects for truth until either they all test True or a timeout is hit. These objects are typically Predicates but could be any object able to be tested for truth.

Every time a Prerequisite is tested for truth, all contained objects are re-tested.

The following snippet illustrates how a Prerequisite can be created and checked:

check_pred = InterfaceUpPredicate(
                device         = my_device,
                interface_name = if_name)

interfaceIsUpPred = Prerequisite(check_pred, timeout=30)

if interfaceIsUpPred:
    print("Interface came up within 30 seconds.")
else:
    print("Interface did not come up within 30 seconds.")

The assert_test method is most commonly used to check a pre-requisite. If the pre-requisite fails then an exception is raised that carries with it debug information describing the failure reason:

try:
    interfaceIsUpPred.assert_test()
except PredicateTestedFalseSignal as e:
    log.error(e)
    print("Interface did not come up within 30 seconds."

PrerequisiteWhile

This is a looping predicate that ensures a series of user-defined objects all continue to test True for a minimum length of time.

The following snippet shows how the PrerequisiteWhile class may be used to ensure an interface stays up for a minimum length of time. Notice the assert_test method also accepts a user-defined failure string that is contained by the exception produced should the assertion fail.

check_pred = InterfaceUpPredicate(
                device         = my_device,
                interface_name = if_name)

interfaceStaysUpPred = PrerequisiteWhile(check_pred, timeout=30)
try:
    interfaceStaysUpPred.assert_test(
        "Interface did not stay up for 30 seconds.")
except PredicateTestedFalseSignal as e:
    log.error(e)

Finally, here’s an example of a test that ensures a series of interfaces stay up for a minimum length of time. If the test fails, the interface that did not stay up is included in the signal and is logged to facilitate debugging:

interfaceStaysUpPred = PrerequisiteWhile(
        InterfaceUpPredicate (interface_object = interface_1),
        InterfaceUpPredicate (interface_object = interface_2),
        InterfaceUpPredicate (interface_object = interface_3),
        timeout=30)

try:
    interfaceStaysUpPred.assert_test(
        "Interfaces did not stay up for 30 seconds.")
except PredicateTestedFalseSignal as e:
    log.error(e)

API Reference

Base Class for Truth Testing

class genie.predcore.Predicate

This class allows for testing the truth of some condition.

This class should be inherited and a test method defined that returns True or False as appropriate.

Whenever the object is tested for truth (for example, in an if statement), the test method is automatically called.

The dump method, if overloaded, is called automatically when appropriate and allows the user to express the object’s state in an easy-to-read manner to assist with debugging and failure diagnosis.

test()

Override this method to actually perform the predicate test.

dump()

Dump the predicate’s internal state for debugging and logging purposes.

assert_test(*args)

Perform a truth test on the predicate, and if it does not test True, raise PredicateTestedFalseSignal.

The result of dump is saved in the exception for debug purposes.

Parameters

args (str) – User-defined positional arguments to be passed to PredicateTestedFalseSignal if the predicate does not test True.

class genie.predcore.PredicateTestedFalseSignal(value='', *args)
Signal raised when assert_test is called on a

Predicate that tests False. As described in result_behavior, the user is expected to catch this signal.

__init__ of PredicateTestedFalseSignal

Allows to capture extra arguments that were passed to this Exception. Joins all the extra argument into self.args tuple to display to the user

Classes for Looped / Timed Predicate Testing

class genie.predcore.Prerequisite(*test_args, **kwargs)

Periodically test a series of objects for truth until they all test True or a timeout expires.

This predicate tests True if all objects in the series test True.

This predicate tests False if one or more objects in the series continue to test False until a timeout expires.

If any of the objects in the series has a dump method (for example, the object inherits from Predicate), this method is automatically invoked in order to provide debug information to the user.

Pre-requisites once instantiated can be tested multiple times. Each time the truth of the pre-requisite is taken all the objects are scheduled for retesting.

Objects are tested in the order passed. Once an object becomes True it is no longer tested (i.e., it is removed from the list of objects to be tested for a given test of the Prerequisite).

Parameters
  • test_args (series of positional arguments containing a series of objects to test) – All positional arguments are first flattened into a single list before they are tested for truth.

  • timeout (int) – The maximum amount of time in seconds to continue testing the objects in the series, measured from the time the last truth test was invoked. Defaults to None.

  • interval (int) – The interval in seconds for which the remaining objects are rechecked. Defaults to 2.

  • iterations (int) – The maximum number of times to check the objects. Defaults to None.

Note

You must specify either timeout or iterations, but you cannot specify both, otherwise a ValueError is raised.

Note

If timeout is less than interval a warning is logged explaining that un-necessary waiting may occur. If interval is left at its default, it is automatically decreased to the value of timeout.

property time_remaining

Calculate the time remaining in the predicate. Since timed truth tests are typically blocking operations, this is typically checked before or after a truth test has been executed. It may also be useful during debugging.

assert_test(*args)

Perform a truth test on the predicate, and if it does not test True, raise PredicateTestedFalseSignal.

The result of dump is saved in the exception for debug purposes.

Parameters

args (str) – User-defined positional arguments to be passed to PredicateTestedFalseSignal if the predicate does not test True.

class genie.predcore.PrerequisiteWhile(*test_args, **kwargs)

Periodically test a series of objects for truth until either a timeout expires or one or more objects test False.

This predicate tests True if all objects in the series continue to test True until a timeout expires.

This predicate tests False if one or more objects in the series test False.

If any of the input objects has a dump method (for example, the object inherits from Predicate), this method is automatically invoked in order to provide debug information to the user.

Pre-requisites once instantiated can be tested multiple times. Each time the truth of the pre-requisite is taken all the objects are scheduled for retesting.

Objects are tested in the order passed.

Parameters
  • test_args (series of positional arguments containing a series of objects to test) – All positional arguments are first flattened into a single list before they are tested for truth.

  • timeout (int) – The maximum amount of time in seconds to continue testing the objects in the series, measured from the time the last truth test was invoked. Defaults to None.

  • interval (int) – The interval in seconds for which the remaining objects are rechecked. Defaults to 2.

  • iterations (int) – The maximum number of times to check the objects. Defaults to None.

Note

You must specify either timeout or iterations, but you cannot specify both, otherwise a ValueError is raised.

Note

If timeout is less than interval a warning is logged explaining that un-necessary waiting may occur. If interval is left at its default, it is automatically decreased to the value of timeout.

property time_remaining

Calculate the time remaining in the predicate. Since timed truth tests are typically blocking operations, this is typically checked before or after a truth test has been executed. It may also be useful during debugging.

assert_test(*args)

Perform a truth test on the predicate, and if it does not test True, raise PredicateTestedFalseSignal.

The result of dump is saved in the exception for debug purposes.

Parameters

args (str) – User-defined positional arguments to be passed to PredicateTestedFalseSignal if the predicate does not test True.

Classes for Logical Predicate Testing

class genie.predcore.AndPredicate(*test_args)

Logical AND of a series of objects.

Each object in the series is tested in order until one evaluates to False.

This predicate tests True only when all objects in the series test True.

This predicate tests False if at least one object in the series tests False.

If any object in the series inherits from Predicate then its dump method is called when appropriate.

This predicate can be seen as wrapping/containing the series of objects.

The series of objects is scheduled for retesting each time the predicate is tested for truth.

Parameters

test_args (series of positional arguments) – All positional arguments are considered as part of a list of objects that must all test True in order for this predicate to test True. All positional arguments are first flattened into a single list before they are tested for truth.

assert_test(*args)

Perform a truth test on the predicate, and if it does not test True, raise PredicateTestedFalseSignal.

The result of dump is saved in the exception for debug purposes.

Parameters

args (str) – User-defined positional arguments to be passed to PredicateTestedFalseSignal if the predicate does not test True.

class genie.predcore.OrPredicate(*test_args)

Logical OR of a series of objects.

Each object in the series is tested in order until one evaluates to True.

This predicate tests True if at least one object in the series tests True.

This predicate tests False only when all objects in the series test False.

If any object in the series inherits from Predicate then its dump method is called when appropriate.

This predicate can be seen as wrapping/containing the series of objects.

The series of objects is scheduled for retesting each time the predicate is tested for truth.

Parameters

test_args (series of positional arguments) – All positional arguments are considered as part of a list of objects that must all test False in order for this predicate to test False. All positional arguments are first flattened into a single list before they are tested for truth.

assert_test(*args)

Perform a truth test on the predicate, and if it does not test True, raise PredicateTestedFalseSignal.

The result of dump is saved in the exception for debug purposes.

Parameters

args (str) – User-defined positional arguments to be passed to PredicateTestedFalseSignal if the predicate does not test True.

class genie.predcore.NotPredicate(object_to_test)

Logical NOT of an object

This predicate tests True if the input object tests False.

This predicate tests False if the input object tests True.

This predicate can be seen as wrapping/containing the input object.

If the input object inherits from Predicate then its dump method is called when appropriate.

Parameters

object_to_test (object) –

assert_test(*args)

Perform a truth test on the predicate, and if it does not test True, raise PredicateTestedFalseSignal.

The result of dump is saved in the exception for debug purposes.

Parameters

args (str) – User-defined positional arguments to be passed to PredicateTestedFalseSignal if the predicate does not test True.

List and Dictionary Comparison Classes

class genie.predcore.ListEqualPredicate(actual_list, expected_list)

An object of this class tests True when an actual and an expected list are compared and are found to contain identical items.

The order of each list’s items is ignored and all duplicate items are removed.

Each list must contain a series of objects that, once flattened, are hashable. If not, an exception is raised on construction.

For example:

>>> from predcore import ListEqualPredicate
>>> pred = ListEqualPredicate([1,2,3], [3,[2,1]])
>>> True if pred else False
True

>>> pred = ListEqualPredicate([1,2,3], [4,3,2,1])
>>> True if pred else False
False

>>> print(pred.dump())
'ListEqualPredicate (
(test if actual [1, 2, 3]
== expected [1, 2, 3, 4]),
AreNotEqual : (
ItemsInExpectedButNotInActual : [4]))'
Parameters
  • actual_list (Iterable) – The actual list to check for equality. All items are first flattened into a single list before they are compared.

  • expected_list (Iterable) – The expected list to check for equality. All items are first flattened into a single list before they are compared.

A ValueError is raised if one or both of the inputs are not iterables.

assert_test(*args)

Perform a truth test on the predicate, and if it does not test True, raise PredicateTestedFalseSignal.

The result of dump is saved in the exception for debug purposes.

Parameters

args (str) – User-defined positional arguments to be passed to PredicateTestedFalseSignal if the predicate does not test True.

class genie.predcore.DictEqualPredicate(actual_dict, expected_dict)

An object of this class tests True when an actual and an expected dictionary are compared and are found to be equal.

For example:

>>> from predcore import DictEqualPredicate
>>> pred = DictEqualPredicate(
    {'first' : 1, 'second' : 2, 'third' : 3},
    {'third' : 3, 'second' : 2, 'first' : 1})
>>> True if pred else False
True

>>> pred = DictEqualPredicate(
    {'first' : 1, 'second' : 2, 'third' : 3},
    {'third' : 30, 'second' : 2, 'first' : 1, 'fourth' : 4})
>>> True if pred else False
False

>>> pred.dump()
"DictEqualPredicate (
(test if actual ['second', 'third', 'first']
== expected ['second', 'third', 'fourth', 'first']),
AreNotEqual : (
ItemsInExpectedButNotInActual : ['fourth'],
UnexpectedValue (Item third has value 3 (expected 30))))"
Parameters
  • actual_dict (Mapping) – The actual dictionary to check for equality.

  • expected_dict (Mapping) – The expected dictionary to check for equality.

A ValueError is raised if one or both of the inputs are not dictionaries.

assert_test(*args)

Perform a truth test on the predicate, and if it does not test True, raise PredicateTestedFalseSignal.

The result of dump is saved in the exception for debug purposes.

Parameters

args (str) – User-defined positional arguments to be passed to PredicateTestedFalseSignal if the predicate does not test True.

class genie.predcore.IsSequenceEqualDiffPredicate(actual_sequence, expected_sequence, context_lines=1)

An object of this class tests True when an actual list is equal to an expected list.

If they are not equal, the dump method contains the differences, expressed in a diff-like format. It shows how one would transform the actual list into the expected list by adding and removing elements from the actual list.

Each list must contain a series of objects that, once flattened, are hashable. If not, an exception is raised on construction.

By default, a nonzero context_lines excludes the majority of the unchanged items from the diff and clusters are presented and separated by “@@” lines detailing the diff’s current slice within each sequence (this is similar to the concept of a ‘unified diff’):

>>> from predcore import IsSequenceEqualDiffPredicate
>>> pred = IsSequenceEqualDiffPredicate([1,2,3,4,5], [1,2,3,4,5])
>>> True if pred else False
True

>>> pred = IsSequenceEqualDiffPredicate([4,5,6,7], [1,2,3,4,5,6,7,8,9,10])
>>> True if pred else False
False

>>> print(pred.dump())
IsSequenceEqualDiffPredicate (
(test if actual [4, 5, 6, 7] == expected [1,2,3,4,5,6,7,8,9,10]),
AreNotEqual : (
@@ -[0:0], +[0:3] @@
+[1, 2, 3]
 [4]
@@ -[3:4], +[6:7] @@
 [7]
+[8, 9, 10]
))

It is also possible to disable the context_lines feature and just request a straight diff:

>>> pred = IsSequenceEqualDiffPredicate([4,5,6,7], [1,2,3,4,5,6,7,8,9,10],
    context_lines=0)
>>> True if pred else False
False

>>> print(pred.dump())
IsSequenceEqualDiffPredicate (
(test if actual [4, 5, 6, 7] == expected [1,2,3,4,5,6,7,8,9,10]),
AreNotEqual : (
+[1, 2, 3]
 [4, 5, 6, 7]
+[8, 9, 10]
))
Parameters
  • actual_sequence (Sequence) – The actual sequence to check for equality. All items are first flattened into a single list before they are compared.

  • expected_sequence (Sequence) – The expected sequence to check for equality. All items are first flattened into a single list before they are compared.

  • context_lines (int) – The number of unchanged elements to show before and after a cluster of diffs. If set to 0, then no clustering is done and all elements are included in the diff. This parameter defaults to 1.

A ValueError is raised if one or both of the inputs are not sequences.

assert_test(*args)

Perform a truth test on the predicate, and if it does not test True, raise PredicateTestedFalseSignal.

The result of dump is saved in the exception for debug purposes.

Parameters

args (str) – User-defined positional arguments to be passed to PredicateTestedFalseSignal if the predicate does not test True.

Generic Function Call Response Checking Class

class genie.predcore.FunctionCallEqualsPredicate(function, expected_function_result, *args, **kwargs)

Predicate that, when tested, calls a user-specified function with user-specified arguments.

The predicate tests True if the function returns the expected result.

The predicate tests False if the function does not return the expected result.

For example:

>>> from predcore import FunctionCallEqualsPredicate

>>> def isEven(number):
...   return (number % 2) == 0

>>> pred = FunctionCallEqualsPredicate(isEven, True, 1)
>>> True if pred else False
False

>>> pred = FunctionCallEqualsPredicate(isEven, True, 4)
>>> True if pred else False
True

Another example of using this predicate to test for a regex match (also showing use of keyword arguments):

>>> import re
>>> pred = FunctionCallEqualsPredicate(function=re.findall,
...  expected_function_result=[],
...  pattern='banana',string='in the apple orchard')

>>> True if pred else False
True

>>> pred = FunctionCallEqualsPredicate(function=re.findall,
...  expected_function_result=['apple'],
...  pattern='apple',string='in the apple orchard')

>>> True if pred else False
True

>>> pred.dump()
"FunctionCallEqualsPredicate (
(findall (pattern='banana', string='in the apple orchard', flags=0) == []),
ComparisonSucceeded)"

Another example of using this predicate to do numeric comparisons. Let’s try “less than”:

>>> pred = FunctionCallEqualsPredicate(lambda a,b: a < b, True, 1, 2)
>>> True if pred else False
True

>>> pred = FunctionCallEqualsPredicate(lambda a,b: a < b, True, 2, 1)
>>> True if pred else False
False
Parameters
  • function – The function to call.

  • expected_function_result – if function returns this expected result then the predicate tests True.

  • args (variable number of positional args) – positional arguments to pass to function.

  • kwargs (variable number of keyword args) – keyword arguments to pass to function.

assert_test(*args)

Perform a truth test on the predicate, and if it does not test True, raise PredicateTestedFalseSignal.

The result of dump is saved in the exception for debug purposes.

Parameters

args (str) – User-defined positional arguments to be passed to PredicateTestedFalseSignal if the predicate does not test True.

Other Predicate Classes

class genie.predcore.InPredicate(candidate_member_object, sequence)

Predicate that tests membership of a candidate object in a sequence

This predicate tests True if the candidate object is a member of the sequence, otherwise it tests False.

Parameters
  • candidate_member_object (object) –

  • sequence (Iterable such as str, list or tuple.) –

assert_test(*args)

Perform a truth test on the predicate, and if it does not test True, raise PredicateTestedFalseSignal.

The result of dump is saved in the exception for debug purposes.

Parameters

args (str) – User-defined positional arguments to be passed to PredicateTestedFalseSignal if the predicate does not test True.

class genie.predcore.InRangePredicate(number, *args)

Predicate that, when tested, verifies that a given integer belongs to one or more user-specified ranges.

NOTE, in Python, ranges are half-open, for example:

>>> 1 in range(1,10)
True

>>> 9 in range(1,10)
True

>>> 10 in range(1,10)
False

This predicate tests True if the number is within the specified range, or is within at least one of a series of specified ranges.

This predicate tests False if the number is outside the specified range or ranges.

For example:

from predcore import InRangePredicate

allowable_numbers=[]
allowable_ranges = [range(1,3), range(7,15)]
for number in range(1,10):
    pred = InRangePredicate(number, *allowable_ranges)
    if pred:
        allowable_numbers.append(number)
print ("Allowable numbers : {}".format(allowable_numbers))

Output from the previous example:

Allowable numbers : [1, 2, 7, 8, 9]
Parameters
  • number (int) – The number to check.

  • args (variable number of positional range args) – A series of range arguments.

assert_test(*args)

Perform a truth test on the predicate, and if it does not test True, raise PredicateTestedFalseSignal.

The result of dump is saved in the exception for debug purposes.

Parameters

args (str) – User-defined positional arguments to be passed to PredicateTestedFalseSignal if the predicate does not test True.

class genie.predcore.IsSubsetPredicate(first_iterable, is_subset_of_iterable)

An object of this class tests True when an iterable is a subset of another iterable.

The order of each list’s items is ignored and all duplicate items are removed.

Each list must contain a series of objects that, once flattened, are hashable. If not, an exception is raised on construction.

For example:

>>> from predcore import IsSubsetPredicate
>>> pred = IsSubsetPredicate([1,2,3], [5,4,3,2,1])
>>> True if pred else False
True

>>> pred = IsSubsetPredicate([1,2,3], [4,3,1])
>>> True if pred else False
False
Parameters
  • first_iterable (Iterable) – The iterable whose items are tested as being a potential subset of is_subset_of_iterable. All items are first flattened into a single list before they are compared.

  • is_subset_of_iterable (Iterable) – The items in first_iterable are tested as being a potential subset of this iterable. All items are first flattened into a single list before they are compared.

A ValueError is raised if one or both of the inputs are not iterables.

assert_test(*args)

Perform a truth test on the predicate, and if it does not test True, raise PredicateTestedFalseSignal.

The result of dump is saved in the exception for debug purposes.

Parameters

args (str) – User-defined positional arguments to be passed to PredicateTestedFalseSignal if the predicate does not test True.

class genie.predcore.IsSupersetPredicate(first_iterable, is_superset_of_iterable)

An object of this class tests True when an iterable is a superset of another iterable.

The order of each list’s items is ignored and all duplicate items are removed.

Each list must contain a series of objects that, once flattened, are hashable. If not, an exception is raised on construction.

For example:

>>> from predcore import IsSupersetPredicate
>>> pred = IsSupersetPredicate([1,2,3,4,5], [3,2])
>>> True if pred else False
True

>>> pred = IsSupersetPredicate([1,2,3], [4,3,2,1])
>>> True if pred else False
False
Parameters
  • first_iterable (Iterable) – The iterable whose items are tested as being a potential superset of is_superset_of_iterable. All items are first flattened into a single list before they are compared.

  • is_superset_of_iterable (Iterable) – The items in first_iterable are tested as being a potential superset of this iterable. All items are first flattened into a single list before they are compared.

A ValueError is raised if one or both of the inputs are not iterables.

assert_test(*args)

Perform a truth test on the predicate, and if it does not test True, raise PredicateTestedFalseSignal.

The result of dump is saved in the exception for debug purposes.

Parameters

args (str) – User-defined positional arguments to be passed to PredicateTestedFalseSignal if the predicate does not test True.