1#!/usr/bin/env python 2 3import itertools 4 5class Condition(object): 6 """Something asserted to be true during the test. 7 8 A given condition may be used as a precondition or a 9 postcondition.""" 10 11 def __call__(self, k, state): 12 """Called with a key and a state. True if the condition is met.""" 13 return True 14 15class Effect(object): 16 """The affect an action will perform.""" 17 18 def __call__(self, k, state): 19 """Called with a key and a state. 20 21 The effect modifies the state as appropriate.""" 22 23class Action(object): 24 """Actions are the operations that will be permuted into test cases. 25 26 Each action has a collection of preconditions and postconditions 27 that will be evaluated for checking input and output state for the 28 action. 29 30 Action.preconditions is the collection of conditions that must all 31 be true upon input to the action. If any condition is not true, 32 the effect is not executed and the action state is considered 33 "errored." 34 35 Action.effect is the callable that is expected to alter the state 36 to satisfy the postconditions of the action. 37 38 Action.postconditions is the collection of conditions that must 39 all be true after the effect of the action completes. 40 """ 41 42 preconditions = [] 43 effect = None 44 postconditions = [] 45 enabled = True 46 47 @property 48 def name(self): 49 """The name of this action (default derived from class name)""" 50 n = self.__class__.__name__ 51 return n[0].lower() + n[1:] 52 53class Driver(object): 54 """The driver "performs" the test.""" 55 56 def newState(self): 57 """Initialize and return the state for a test.""" 58 return {} 59 60 def preSuite(self, seq): 61 """Invoked with the sequence of tests before any are run.""" 62 63 def startSequence(self, seq): 64 """Invoked with the sequence of actions in a single test 65 before it is performed.""" 66 67 def startAction(self, action): 68 """Invoked when before starting an action.""" 69 70 def endAction(self, action, state, errored): 71 """Invoked after the action is performed.""" 72 73 def endSequence(self, seq, state): 74 """Invoked at the end of a sequence of tests.""" 75 76 def postSuite(self, seq): 77 """Invoked with the sequence of tests after all of them are run.""" 78 79def runTest(actions, driver, duplicates=3, length=4): 80 """Run a test with the given collection of actions and driver. 81 82 The optional argument `duplicates' specifies how many times a 83 given action may be duplicated in a sequence. 84 85 The optional argument `length` specifies how long each test 86 sequence is. 87 """ 88 89 instances = itertools.chain(*itertools.repeat([a() for a in actions], 90 duplicates)) 91 tests = set(itertools.permutations(instances, length)) 92 driver.preSuite(tests) 93 for seq in sorted(tests): 94 state = driver.newState() 95 driver.startSequence(seq) 96 for a in seq: 97 driver.startAction(a) 98 haserror = not all(p(state) for p in a.preconditions) 99 if not haserror: 100 try: 101 a.effect(state) 102 haserror = not all(p(state) for p in a.postconditions) 103 except: 104 haserror = True 105 driver.endAction(a, state, haserror) 106 driver.endSequence(seq, state) 107 driver.postSuite(tests) 108 109def findActions(classes): 110 """Helper function to extract action subclasses from a collection 111 of classes.""" 112 113 actions = [] 114 for __t in (t for t in classes if isinstance(type, type(t))): 115 if Action in __t.__mro__ and __t != Action and __t.enabled: 116 actions.append(__t) 117 return actions 118