class RuleBasedStateMachine(metaclass=StateMachineMeta): """A RuleBasedStateMachine gives you a structured way to define state machines. The idea is that a state machine carries a bunch of types of data divided into Bundles, and has a set of rules which may read data from bundles (or just from normal strategies) and push data onto bundles. At any given point a random applicable rule will be executed. """ _rules_per_class = {} # type: Dict[type, List[classmethod]] _invariants_per_class = {} # type: Dict[type, List[classmethod]] _base_rules_per_class = {} # type: Dict[type, List[classmethod]] _initializers_per_class = {} # type: Dict[type, List[classmethod]] _base_initializers_per_class = {} # type: Dict[type, List[classmethod]] def __init__(self): if not self.rules(): raise InvalidDefinition("Type %s defines no rules" % (type(self).__name__, )) self.bundles = {} # type: Dict[str, list] self.name_counter = 1 self.names_to_values = {} # type: Dict[str, Any] self.__stream = StringIO() self.__printer = RepresentationPrinter(self.__stream) self._initialize_rules_to_run = copy(self.initialize_rules()) self._rules_strategy = RuleStrategy(self) def _pretty_print(self, value): if isinstance(value, VarReference): return value.name self.__stream.seek(0) self.__stream.truncate(0) self.__printer.output_width = 0 self.__printer.buffer_width = 0 self.__printer.buffer.clear() self.__printer.pretty(value) self.__printer.flush() return self.__stream.getvalue() def __repr__(self): return "%s(%s)" % (type(self).__name__, nicerepr(self.bundles)) def _new_name(self): result = "v%d" % (self.name_counter, ) self.name_counter += 1 return result def _last_names(self, n): assert self.name_counter > n count = self.name_counter return ["v%d" % (i, ) for i in range(count - n, count)] def bundle(self, name): return self.bundles.setdefault(name, []) @classmethod def initialize_rules(cls): try: return cls._initializers_per_class[cls] except KeyError: pass for _, v in inspect.getmembers(cls): r = getattr(v, INITIALIZE_RULE_MARKER, None) if r is not None: cls.define_initialize_rule(r.targets, r.function, r.arguments, r.precondition) cls._initializers_per_class[ cls] = cls._base_initializers_per_class.pop(cls, []) return cls._initializers_per_class[cls] @classmethod def rules(cls): try: return cls._rules_per_class[cls] except KeyError: pass for _, v in inspect.getmembers(cls): r = getattr(v, RULE_MARKER, None) if r is not None: cls.define_rule(r.targets, r.function, r.arguments, r.precondition) cls._rules_per_class[cls] = cls._base_rules_per_class.pop(cls, []) return cls._rules_per_class[cls] @classmethod def invariants(cls): try: return cls._invariants_per_class[cls] except KeyError: pass target = [] for _, v in inspect.getmembers(cls): i = getattr(v, INVARIANT_MARKER, None) if i is not None: target.append(i) cls._invariants_per_class[cls] = target return cls._invariants_per_class[cls] @classmethod def define_initialize_rule(cls, targets, function, arguments, precondition=None): converted_arguments = {} for k, v in arguments.items(): converted_arguments[k] = v if cls in cls._initializers_per_class: target = cls._initializers_per_class[cls] else: target = cls._base_initializers_per_class.setdefault(cls, []) return target.append( Rule(targets, function, converted_arguments, precondition)) @classmethod def define_rule(cls, targets, function, arguments, precondition=None): converted_arguments = {} for k, v in arguments.items(): converted_arguments[k] = v if cls in cls._rules_per_class: target = cls._rules_per_class[cls] else: target = cls._base_rules_per_class.setdefault(cls, []) return target.append( Rule(targets, function, converted_arguments, precondition)) def _print_step(self, rule, data, result): self.step_count = getattr(self, "step_count", 0) + 1 # If the step has target bundles, and the result is a MultipleResults # then we want to assign to multiple variables. if isinstance(result, MultipleResults): n_output_vars = len(result.values) else: n_output_vars = 1 output_assignment = ("%s = " % (", ".join(self._last_names(n_output_vars)), ) if rule.targets and n_output_vars >= 1 else "") report("%sstate.%s(%s)" % ( output_assignment, rule.function.__name__, ", ".join("%s=%s" % kv for kv in data.items()), )) def _add_result_to_targets(self, targets, result): name = self._new_name() self.__printer.singleton_pprinters.setdefault( id(result), lambda obj, p, cycle: p.text(name)) self.names_to_values[name] = result for target in targets: self.bundles.setdefault(target, []).append(VarReference(name)) def check_invariants(self): for invar in self.invariants(): if invar.precondition and not invar.precondition(self): continue invar.function(self) def teardown(self): """Called after a run has finished executing to clean up any necessary state. Does nothing by default. """ TestCase = TestCaseProperty() @classmethod @lru_cache() def _to_test_case(state_machine_class): class StateMachineTestCase(TestCase): settings = Settings(deadline=None, suppress_health_check=HealthCheck.all()) def runTest(self): run_state_machine_as_test(state_machine_class) runTest.is_hypothesis_test = True StateMachineTestCase.__name__ = state_machine_class.__name__ + ".TestCase" StateMachineTestCase.__qualname__ = qualname( state_machine_class) + ".TestCase" return StateMachineTestCase
def run(data): # Set up dynamic context needed by a single test run. with local_settings(self.settings): with deterministic_PRNG(): with BuildContext(data, is_final=is_final): # Generate all arguments to the test function. args, kwargs = data.draw(self.search_strategy) if expected_failure is not None: text_repr[0] = arg_string(test, args, kwargs) if print_example or current_verbosity( ) >= Verbosity.verbose: output = CUnicodeIO() printer = RepresentationPrinter(output) if print_example: printer.text("Falsifying example:") else: printer.text("Trying example:") if self.print_given_args: printer.text(" ") printer.text(test.__name__) with printer.group(indent=4, open="(", close=""): printer.break_() for v in args: printer.pretty(v) # We add a comma unconditionally because # generated arguments will always be # kwargs, so there will always be more # to come. printer.text(",") printer.breakable() # We need to make sure to print these in the argument order for # Python 2 and older versionf of Python 3.5. In modern versions # this isn't an issue because kwargs is ordered. arg_order = { v: i for i, v in enumerate( getfullargspec(self.test).args) } for i, (k, v) in enumerate( sorted( kwargs.items(), key=lambda t: ( arg_order.get( t[0], float("inf")), t[0], ), )): printer.text(k) printer.text("=") printer.pretty(v) printer.text(",") if i + 1 < len(kwargs): printer.breakable() printer.break_() printer.text(")") printer.flush() report(output.getvalue()) return test(*args, **kwargs)
class RuleBasedStateMachine(GenericStateMachine): """A RuleBasedStateMachine gives you a more structured way to define state machines. The idea is that a state machine carries a bunch of types of data divided into Bundles, and has a set of rules which may read data from bundles (or just from normal strategies) and push data onto bundles. At any given point a random applicable rule will be executed. """ _rules_per_class = {} _invariants_per_class = {} _base_rules_per_class = {} def __init__(self): if not self.rules(): raise InvalidDefinition(u'Type %s defines no rules' % ( type(self).__name__, )) self.bundles = {} self.name_counter = 1 self.names_to_values = {} self.__stream = CUnicodeIO() self.__printer = RepresentationPrinter(self.__stream) def __pretty(self, value): self.__stream.seek(0) self.__stream.truncate(0) self.__printer.output_width = 0 self.__printer.buffer_width = 0 self.__printer.buffer.clear() self.__printer.pretty(value) self.__printer.flush() return self.__stream.getvalue() def __repr__(self): return u'%s(%s)' % ( type(self).__name__, nicerepr(self.bundles), ) def upcoming_name(self): return u'v%d' % (self.name_counter,) def new_name(self): result = self.upcoming_name() self.name_counter += 1 return result def bundle(self, name): return self.bundles.setdefault(name, []) @classmethod def rules(cls): try: return cls._rules_per_class[cls] except KeyError: pass for k, v in inspect.getmembers(cls): r = getattr(v, RULE_MARKER, None) if r is not None: cls.define_rule( r.targets, r.function, r.arguments, r.precondition, ) cls._rules_per_class[cls] = cls._base_rules_per_class.pop(cls, []) return cls._rules_per_class[cls] @classmethod def invariants(cls): try: return cls._invariants_per_class[cls] except KeyError: pass target = [] for k, v in inspect.getmembers(cls): i = getattr(v, INVARIANT_MARKER, None) if i is not None: target.append(i) cls._invariants_per_class[cls] = target return cls._invariants_per_class[cls] @classmethod def define_rule(cls, targets, function, arguments, precondition=None): converted_arguments = {} for k, v in arguments.items(): converted_arguments[k] = v if cls in cls._rules_per_class: target = cls._rules_per_class[cls] else: target = cls._base_rules_per_class.setdefault(cls, []) return target.append( Rule( targets, function, converted_arguments, precondition, ) ) def steps(self): strategies = [] for rule in self.rules(): converted_arguments = {} valid = True if rule.precondition and not rule.precondition(self): continue for k, v in sorted(rule.arguments.items()): if isinstance(v, Bundle): bundle = self.bundle(v.name) if not bundle: valid = False break converted_arguments[k] = v if valid: strategies.append(TupleStrategy(( just(rule), FixedKeysDictStrategy(converted_arguments) ), tuple)) if not strategies: raise InvalidDefinition( u'No progress can be made from state %r' % (self,) ) for name, bundle in self.bundles.items(): if len(bundle) > 1: strategies.append( builds( ShuffleBundle, just(name), lists(integers(0, len(bundle) - 1)))) return one_of(strategies) def print_step(self, step): if isinstance(step, ShuffleBundle): return rule, data = step data_repr = {} for k, v in data.items(): data_repr[k] = self.__pretty(v) self.step_count = getattr(self, u'step_count', 0) + 1 report(u'Step #%d: %s%s(%s)' % ( self.step_count, u'%s = ' % (self.upcoming_name(),) if rule.targets else u'', rule.function.__name__, u', '.join(u'%s=%s' % kv for kv in data_repr.items()) )) def execute_step(self, step): if isinstance(step, ShuffleBundle): bundle = self.bundle(step.bundle) for i in step.swaps: bundle.insert(i, bundle.pop()) return rule, data = step data = dict(data) result = rule.function(self, **data) if rule.targets: name = self.new_name() self.names_to_values[name] = result self.__printer.singleton_pprinters.setdefault( id(result), lambda obj, p, cycle: p.text(name), ) for target in rule.targets: self.bundle(target).append(VarReference(name)) def check_invariants(self): for invar in self.invariants(): if invar.precondition and not invar.precondition(self): continue invar.function(self)
class RuleBasedStateMachine(GenericStateMachine): """A RuleBasedStateMachine gives you a more structured way to define state machines. The idea is that a state machine carries a bunch of types of data divided into Bundles, and has a set of rules which may read data from bundles (or just from normal strategies) and push data onto bundles. At any given point a random applicable rule will be executed. """ _rules_per_class = {} # type: Dict[type, List[classmethod]] _invariants_per_class = {} # type: Dict[type, List[classmethod]] _base_rules_per_class = {} # type: Dict[type, List[classmethod]] _initializers_per_class = {} # type: Dict[type, List[classmethod]] _base_initializers_per_class = {} # type: Dict[type, List[classmethod]] def __init__(self): if not self.rules(): raise InvalidDefinition(u'Type %s defines no rules' % (type(self).__name__, )) self.bundles = {} # type: Dict[Text, list] self.name_counter = 1 self.names_to_values = {} # type: Dict[Text, Any] self.__stream = CUnicodeIO() self.__printer = RepresentationPrinter(self.__stream) self._initialize_rules_to_run = copy(self.initialize_rules()) def __pretty(self, value): if isinstance(value, VarReference): return value.name self.__stream.seek(0) self.__stream.truncate(0) self.__printer.output_width = 0 self.__printer.buffer_width = 0 self.__printer.buffer.clear() self.__printer.pretty(value) self.__printer.flush() return self.__stream.getvalue() def __repr__(self): return u'%s(%s)' % ( type(self).__name__, nicerepr(self.bundles), ) def upcoming_name(self): return u'v%d' % (self.name_counter, ) def new_name(self): result = self.upcoming_name() self.name_counter += 1 return result def bundle(self, name): return self.bundles.setdefault(name, []) @classmethod def initialize_rules(cls): try: return cls._initializers_per_class[cls] except KeyError: pass for k, v in inspect.getmembers(cls): r = getattr(v, INITIALIZE_RULE_MARKER, None) if r is not None: cls.define_initialize_rule( r.targets, r.function, r.arguments, r.precondition, ) cls._initializers_per_class[cls] = \ cls._base_initializers_per_class.pop(cls, []) return cls._initializers_per_class[cls] @classmethod def rules(cls): try: return cls._rules_per_class[cls] except KeyError: pass for k, v in inspect.getmembers(cls): r = getattr(v, RULE_MARKER, None) if r is not None: cls.define_rule( r.targets, r.function, r.arguments, r.precondition, ) cls._rules_per_class[cls] = cls._base_rules_per_class.pop(cls, []) return cls._rules_per_class[cls] @classmethod def invariants(cls): try: return cls._invariants_per_class[cls] except KeyError: pass target = [] for k, v in inspect.getmembers(cls): i = getattr(v, INVARIANT_MARKER, None) if i is not None: target.append(i) cls._invariants_per_class[cls] = target return cls._invariants_per_class[cls] @classmethod def define_initialize_rule(cls, targets, function, arguments, precondition=None): converted_arguments = {} for k, v in arguments.items(): converted_arguments[k] = v if cls in cls._initializers_per_class: target = cls._initializers_per_class[cls] else: target = cls._base_initializers_per_class.setdefault(cls, []) return target.append( Rule( targets, function, converted_arguments, precondition, )) @classmethod def define_rule(cls, targets, function, arguments, precondition=None): converted_arguments = {} for k, v in arguments.items(): converted_arguments[k] = v if cls in cls._rules_per_class: target = cls._rules_per_class[cls] else: target = cls._base_rules_per_class.setdefault(cls, []) return target.append( Rule( targets, function, converted_arguments, precondition, )) def steps(self): # Pick initialize rules first if self._initialize_rules_to_run: return one_of([ tuples(just(rule), fixed_dictionaries(rule.arguments)) for rule in self._initialize_rules_to_run ]) # All initialize rules has been run once, go with the regular rules strategies = [] for rule in self.rules(): converted_arguments = {} valid = True if rule.precondition and not rule.precondition(self): continue for k, v in sorted(rule.arguments.items()): if isinstance(v, Bundle): bundle = self.bundle(v.name) if not bundle: valid = False break v = BundleReferenceStrategy(v.name) converted_arguments[k] = v if valid: strategies.append( tuples(just(rule), fixed_dictionaries(converted_arguments))) if not strategies: raise InvalidDefinition(u'No progress can be made from state %r' % (self, )) return one_of(strategies) def print_start(self): report(u'state = %s()' % (self.__class__.__name__, )) def print_end(self): report(u'state.teardown()') def print_step(self, step): rule, data = step data_repr = {} for k, v in data.items(): data_repr[k] = self.__pretty(v) self.step_count = getattr(self, u'step_count', 0) + 1 report( u'%sstate.%s(%s)' % (u'%s = ' % (self.upcoming_name(), ) if rule.targets else u'', rule.function.__name__, u', '.join(u'%s=%s' % kv for kv in data_repr.items()))) def execute_step(self, step): rule, data = step data = dict(data) for k, v in list(data.items()): if isinstance(v, VarReference): data[k] = self.names_to_values[v.name] result = rule.function(self, **data) if rule.targets: name = self.new_name() self.names_to_values[name] = result self.__printer.singleton_pprinters.setdefault( id(result), lambda obj, p, cycle: p.text(name)) for target in rule.targets: self.bundle(target).append(VarReference(name)) if self._initialize_rules_to_run: self._initialize_rules_to_run.remove(rule) def check_invariants(self): for invar in self.invariants(): if invar.precondition and not invar.precondition(self): continue invar.function(self)
class RuleBasedStateMachine(_GenericStateMachine): """A RuleBasedStateMachine gives you a structured way to define state machines. The idea is that a state machine carries a bunch of types of data divided into Bundles, and has a set of rules which may read data from bundles (or just from normal strategies) and push data onto bundles. At any given point a random applicable rule will be executed. """ _rules_per_class = {} # type: Dict[type, List[classmethod]] _invariants_per_class = {} # type: Dict[type, List[classmethod]] _base_rules_per_class = {} # type: Dict[type, List[classmethod]] _initializers_per_class = {} # type: Dict[type, List[classmethod]] _base_initializers_per_class = {} # type: Dict[type, List[classmethod]] def __init__(self): if not self.rules(): raise InvalidDefinition("Type %s defines no rules" % (type(self).__name__, )) self.bundles = {} # type: Dict[str, list] self.name_counter = 1 self.names_to_values = {} # type: Dict[str, Any] self.__stream = StringIO() self.__printer = RepresentationPrinter(self.__stream) self._initialize_rules_to_run = copy(self.initialize_rules()) self.__rules_strategy = RuleStrategy(self) def __pretty(self, value): if isinstance(value, VarReference): return value.name self.__stream.seek(0) self.__stream.truncate(0) self.__printer.output_width = 0 self.__printer.buffer_width = 0 self.__printer.buffer.clear() self.__printer.pretty(value) self.__printer.flush() return self.__stream.getvalue() def __repr__(self): return "%s(%s)" % (type(self).__name__, nicerepr(self.bundles)) def upcoming_name(self): return "v%d" % (self.name_counter, ) def last_names(self, n): assert self.name_counter > n count = self.name_counter return ["v%d" % (i, ) for i in range(count - n, count)] def new_name(self): result = self.upcoming_name() self.name_counter += 1 return result def bundle(self, name): return self.bundles.setdefault(name, []) @classmethod def initialize_rules(cls): try: return cls._initializers_per_class[cls] except KeyError: pass for _, v in inspect.getmembers(cls): r = getattr(v, INITIALIZE_RULE_MARKER, None) if r is not None: cls.define_initialize_rule(r.targets, r.function, r.arguments, r.precondition) cls._initializers_per_class[ cls] = cls._base_initializers_per_class.pop(cls, []) return cls._initializers_per_class[cls] @classmethod def rules(cls): try: return cls._rules_per_class[cls] except KeyError: pass for _, v in inspect.getmembers(cls): r = getattr(v, RULE_MARKER, None) if r is not None: cls.define_rule(r.targets, r.function, r.arguments, r.precondition) cls._rules_per_class[cls] = cls._base_rules_per_class.pop(cls, []) return cls._rules_per_class[cls] @classmethod def invariants(cls): try: return cls._invariants_per_class[cls] except KeyError: pass target = [] for _, v in inspect.getmembers(cls): i = getattr(v, INVARIANT_MARKER, None) if i is not None: target.append(i) cls._invariants_per_class[cls] = target return cls._invariants_per_class[cls] @classmethod def define_initialize_rule(cls, targets, function, arguments, precondition=None): converted_arguments = {} for k, v in arguments.items(): converted_arguments[k] = v if cls in cls._initializers_per_class: target = cls._initializers_per_class[cls] else: target = cls._base_initializers_per_class.setdefault(cls, []) return target.append( Rule(targets, function, converted_arguments, precondition)) @classmethod def define_rule(cls, targets, function, arguments, precondition=None): converted_arguments = {} for k, v in arguments.items(): converted_arguments[k] = v if cls in cls._rules_per_class: target = cls._rules_per_class[cls] else: target = cls._base_rules_per_class.setdefault(cls, []) return target.append( Rule(targets, function, converted_arguments, precondition)) def steps(self): # Pick initialize rules first if self._initialize_rules_to_run: return st.one_of([ st.tuples(st.just(rule), st.fixed_dictionaries(rule.arguments)) for rule in self._initialize_rules_to_run ]) return self.__rules_strategy def print_start(self): report("state = %s()" % (self.__class__.__name__, )) def print_end(self): report("state.teardown()") def print_step(self, step, result): rule, data = step data_repr = {} for k, v in data.items(): data_repr[k] = self.__pretty(v) self.step_count = getattr(self, "step_count", 0) + 1 # If the step has target bundles, and the result is a MultipleResults # then we want to assign to multiple variables. if isinstance(result, MultipleResults): n_output_vars = len(result.values) else: n_output_vars = 1 output_assignment = ("%s = " % (", ".join(self.last_names(n_output_vars)), ) if rule.targets and n_output_vars >= 1 else "") report("%sstate.%s(%s)" % ( output_assignment, rule.function.__name__, ", ".join("%s=%s" % kv for kv in data_repr.items()), )) def _add_result_to_targets(self, targets, result): name = self.new_name() self.__printer.singleton_pprinters.setdefault( id(result), lambda obj, p, cycle: p.text(name)) self.names_to_values[name] = result for target in targets: self.bundle(target).append(VarReference(name)) def execute_step(self, step): rule, data = step data = dict(data) for k, v in list(data.items()): if isinstance(v, VarReference): data[k] = self.names_to_values[v.name] result = rule.function(self, **data) if rule.targets: if isinstance(result, MultipleResults): for single_result in result.values: self._add_result_to_targets(rule.targets, single_result) else: self._add_result_to_targets(rule.targets, result) if self._initialize_rules_to_run: self._initialize_rules_to_run.remove(rule) return result def check_invariants(self): for invar in self.invariants(): if invar.precondition and not invar.precondition(self): continue invar.function(self)
class RuleBasedStateMachine(GenericStateMachine): """A RuleBasedStateMachine gives you a more structured way to define state machines. The idea is that a state machine carries a bunch of types of data divided into Bundles, and has a set of rules which may read data from bundles (or just from normal strategies) and push data onto bundles. At any given point a random applicable rule will be executed. """ _rules_per_class = {} _base_rules_per_class = {} def __init__(self): if not self.rules(): raise InvalidDefinition(u'Type %s defines no rules' % ( type(self).__name__, )) self.bundles = {} self.name_counter = 1 self.names_to_values = {} self.__stream = CUnicodeIO() self.__printer = RepresentationPrinter(self.__stream) def __pretty(self, value): self.__stream.seek(0) self.__stream.truncate(0) self.__printer.output_width = 0 self.__printer.buffer_width = 0 self.__printer.buffer.clear() self.__printer.pretty(value) self.__printer.flush() return self.__stream.getvalue() def __repr__(self): return u'%s(%s)' % ( type(self).__name__, nicerepr(self.bundles), ) def upcoming_name(self): return u'v%d' % (self.name_counter,) def new_name(self): result = self.upcoming_name() self.name_counter += 1 return result def bundle(self, name): return self.bundles.setdefault(name, []) @classmethod def rules(cls): try: return cls._rules_per_class[cls] except KeyError: pass for k, v in inspect.getmembers(cls): r = getattr(v, RULE_MARKER, None) while r is not None: cls.define_rule( r.targets, r.function, r.arguments, r.precondition, r.parent_rule ) r = r.parent_rule cls._rules_per_class[cls] = cls._base_rules_per_class.pop(cls, []) return cls._rules_per_class[cls] @classmethod def define_rule(cls, targets, function, arguments, precondition=None, parent_rule=None): converted_arguments = {} for k, v in arguments.items(): converted_arguments[k] = v if cls in cls._rules_per_class: target = cls._rules_per_class[cls] else: target = cls._base_rules_per_class.setdefault(cls, []) return target.append( Rule( targets, function, converted_arguments, precondition, parent_rule ) ) def steps(self): strategies = [] for rule in self.rules(): converted_arguments = {} valid = True if rule.precondition is not None and not rule.precondition(self): continue for k, v in sorted(rule.arguments.items()): if isinstance(v, Bundle): bundle = self.bundle(v.name) if not bundle: valid = False break converted_arguments[k] = v if valid: strategies.append(TupleStrategy(( just(rule), FixedKeysDictStrategy(converted_arguments) ), tuple)) if not strategies: raise InvalidDefinition( u'No progress can be made from state %r' % (self,) ) for name, bundle in self.bundles.items(): if len(bundle) > 1: strategies.append( builds( ShuffleBundle, just(name), lists(integers(0, len(bundle) - 1)))) return one_of(strategies) def print_step(self, step): if isinstance(step, ShuffleBundle): return rule, data = step data_repr = {} for k, v in data.items(): data_repr[k] = self.__pretty(v) self.step_count = getattr(self, u'step_count', 0) + 1 report(u'Step #%d: %s%s(%s)' % ( self.step_count, u'%s = ' % (self.upcoming_name(),) if rule.targets else u'', rule.function.__name__, u', '.join(u'%s=%s' % kv for kv in data_repr.items()) )) def execute_step(self, step): if isinstance(step, ShuffleBundle): bundle = self.bundle(step.bundle) for i in step.swaps: bundle.insert(i, bundle.pop()) return rule, data = step data = dict(data) result = rule.function(self, **data) if rule.targets: name = self.new_name() self.names_to_values[name] = result self.__printer.singleton_pprinters.setdefault( id(result), lambda obj, p, cycle: p.text(name), ) for target in rule.targets: self.bundle(target).append(VarReference(name))
class RuleBasedStateMachine(metaclass=StateMachineMeta): """A RuleBasedStateMachine gives you a structured way to define state machines. The idea is that a state machine carries a bunch of types of data divided into Bundles, and has a set of rules which may read data from bundles (or just from normal strategies) and push data onto bundles. At any given point a random applicable rule will be executed. """ _rules_per_class: Dict[type, List[classmethod]] = {} _invariants_per_class: Dict[type, List[classmethod]] = {} _initializers_per_class: Dict[type, List[classmethod]] = {} def __init__(self): if not self.rules(): raise InvalidDefinition( f"Type {type(self).__name__} defines no rules") self.bundles: Dict[str, list] = {} self.name_counter = 1 self.names_to_values: Dict[str, Any] = {} self.__stream = StringIO() self.__printer = RepresentationPrinter(self.__stream) self._initialize_rules_to_run = copy(self.initialize_rules()) self._rules_strategy = RuleStrategy(self) def _pretty_print(self, value): if isinstance(value, VarReference): return value.name self.__stream.seek(0) self.__stream.truncate(0) self.__printer.output_width = 0 self.__printer.buffer_width = 0 self.__printer.buffer.clear() self.__printer.pretty(value) self.__printer.flush() return self.__stream.getvalue() def __repr__(self): return f"{type(self).__name__}({nicerepr(self.bundles)})" def _new_name(self): result = f"v{self.name_counter}" self.name_counter += 1 return result def _last_names(self, n): assert self.name_counter > n count = self.name_counter return [f"v{i}" for i in range(count - n, count)] def bundle(self, name): return self.bundles.setdefault(name, []) @classmethod def initialize_rules(cls): try: return cls._initializers_per_class[cls] except KeyError: pass cls._initializers_per_class[cls] = [] for _, v in inspect.getmembers(cls): r = getattr(v, INITIALIZE_RULE_MARKER, None) if r is not None: cls._initializers_per_class[cls].append(r) return cls._initializers_per_class[cls] @classmethod def rules(cls): try: return cls._rules_per_class[cls] except KeyError: pass cls._rules_per_class[cls] = [] for _, v in inspect.getmembers(cls): r = getattr(v, RULE_MARKER, None) if r is not None: cls._rules_per_class[cls].append(r) return cls._rules_per_class[cls] @classmethod def invariants(cls): try: return cls._invariants_per_class[cls] except KeyError: pass target = [] for _, v in inspect.getmembers(cls): i = getattr(v, INVARIANT_MARKER, None) if i is not None: target.append(i) cls._invariants_per_class[cls] = target return cls._invariants_per_class[cls] def _print_step(self, rule, data, result): self.step_count = getattr(self, "step_count", 0) + 1 output_assignment = "" if rule.targets: if isinstance(result, MultipleResults): if len(result.values) == 1: output_assignment = f"({self._last_names(1)[0]},) = " elif result.values: output_names = self._last_names(len(result.values)) output_assignment = ", ".join(output_names) + " = " else: output_assignment = self._last_names(1)[0] + " = " report("{}state.{}({})".format( output_assignment, rule.function.__name__, ", ".join("%s=%s" % kv for kv in data.items()), )) def _add_result_to_targets(self, targets, result): name = self._new_name() self.__printer.singleton_pprinters.setdefault( id(result), lambda obj, p, cycle: p.text(name)) self.names_to_values[name] = result for target in targets: self.bundles.setdefault(target, []).append(VarReference(name)) def check_invariants(self, settings): for invar in self.invariants(): if self._initialize_rules_to_run and not invar.check_during_init: continue if not all(precond(self) for precond in invar.preconditions): continue if (current_build_context().is_final or settings.verbosity >= Verbosity.debug): report(f"state.{invar.function.__name__}()") result = invar.function(self) if result is not None: fail_health_check( settings, "The return value of an @invariant is always ignored, but " f"{invar.function.__qualname__} returned {result!r} " "instead of None", HealthCheck.return_value, ) def teardown(self): """Called after a run has finished executing to clean up any necessary state. Does nothing by default. """ TestCase = TestCaseProperty() @classmethod @lru_cache() def _to_test_case(cls): class StateMachineTestCase(TestCase): settings = Settings(deadline=None, suppress_health_check=HealthCheck.all()) def runTest(self): run_state_machine_as_test(cls) runTest.is_hypothesis_test = True StateMachineTestCase.__name__ = cls.__name__ + ".TestCase" StateMachineTestCase.__qualname__ = cls.__qualname__ + ".TestCase" return StateMachineTestCase
class RuleBasedStateMachine(GenericStateMachine): """A RuleBasedStateMachine gives you a more structured way to define state machines. The idea is that a state machine carries a bunch of types of data divided into Bundles, and has a set of rules which may read data from bundles (or just from normal strategies) and push data onto bundles. At any given point a random applicable rule will be executed. """ _rules_per_class = {} # type: Dict[type, List[classmethod]] _invariants_per_class = {} # type: Dict[type, List[classmethod]] _base_rules_per_class = {} # type: Dict[type, List[classmethod]] _initializers_per_class = {} # type: Dict[type, List[classmethod]] _base_initializers_per_class = {} # type: Dict[type, List[classmethod]] def __init__(self): if not self.rules(): raise InvalidDefinition(u'Type %s defines no rules' % ( type(self).__name__, )) self.bundles = {} # type: Dict[Text, list] self.name_counter = 1 self.names_to_values = {} # type: Dict[Text, Any] self.__stream = CUnicodeIO() self.__printer = RepresentationPrinter(self.__stream) self._initialize_rules_to_run = copy(self.initialize_rules()) self.__rules_strategy = RuleStrategy(self) def __pretty(self, value): if isinstance(value, VarReference): return value.name self.__stream.seek(0) self.__stream.truncate(0) self.__printer.output_width = 0 self.__printer.buffer_width = 0 self.__printer.buffer.clear() self.__printer.pretty(value) self.__printer.flush() return self.__stream.getvalue() def __repr__(self): return u'%s(%s)' % ( type(self).__name__, nicerepr(self.bundles), ) def upcoming_name(self): return u'v%d' % (self.name_counter,) def new_name(self): result = self.upcoming_name() self.name_counter += 1 return result def bundle(self, name): return self.bundles.setdefault(name, []) @classmethod def initialize_rules(cls): try: return cls._initializers_per_class[cls] except KeyError: pass for k, v in inspect.getmembers(cls): r = getattr(v, INITIALIZE_RULE_MARKER, None) if r is not None: cls.define_initialize_rule( r.targets, r.function, r.arguments, r.precondition, ) cls._initializers_per_class[cls] = \ cls._base_initializers_per_class.pop(cls, []) return cls._initializers_per_class[cls] @classmethod def rules(cls): try: return cls._rules_per_class[cls] except KeyError: pass for k, v in inspect.getmembers(cls): r = getattr(v, RULE_MARKER, None) if r is not None: cls.define_rule( r.targets, r.function, r.arguments, r.precondition, ) cls._rules_per_class[cls] = cls._base_rules_per_class.pop(cls, []) return cls._rules_per_class[cls] @classmethod def invariants(cls): try: return cls._invariants_per_class[cls] except KeyError: pass target = [] for k, v in inspect.getmembers(cls): i = getattr(v, INVARIANT_MARKER, None) if i is not None: target.append(i) cls._invariants_per_class[cls] = target return cls._invariants_per_class[cls] @classmethod def define_initialize_rule( cls, targets, function, arguments, precondition=None): converted_arguments = {} for k, v in arguments.items(): converted_arguments[k] = v if cls in cls._initializers_per_class: target = cls._initializers_per_class[cls] else: target = cls._base_initializers_per_class.setdefault(cls, []) return target.append( Rule( targets, function, converted_arguments, precondition, ) ) @classmethod def define_rule(cls, targets, function, arguments, precondition=None): converted_arguments = {} for k, v in arguments.items(): converted_arguments[k] = v if cls in cls._rules_per_class: target = cls._rules_per_class[cls] else: target = cls._base_rules_per_class.setdefault(cls, []) return target.append( Rule( targets, function, converted_arguments, precondition, ) ) def steps(self): # Pick initialize rules first if self._initialize_rules_to_run: return one_of([ tuples(just(rule), fixed_dictionaries(rule.arguments)) for rule in self._initialize_rules_to_run ]) return self.__rules_strategy def print_start(self): report(u'state = %s()' % (self.__class__.__name__,)) def print_end(self): report(u'state.teardown()') def print_step(self, step): rule, data = step data_repr = {} for k, v in data.items(): data_repr[k] = self.__pretty(v) self.step_count = getattr(self, u'step_count', 0) + 1 report(u'%sstate.%s(%s)' % ( u'%s = ' % (self.upcoming_name(),) if rule.targets else u'', rule.function.__name__, u', '.join(u'%s=%s' % kv for kv in data_repr.items()) )) def execute_step(self, step): rule, data = step data = dict(data) for k, v in list(data.items()): if isinstance(v, VarReference): data[k] = self.names_to_values[v.name] result = rule.function(self, **data) if rule.targets: name = self.new_name() self.names_to_values[name] = result self.__printer.singleton_pprinters.setdefault( id(result), lambda obj, p, cycle: p.text(name) ) for target in rule.targets: self.bundle(target).append(VarReference(name)) if self._initialize_rules_to_run: self._initialize_rules_to_run.remove(rule) def check_invariants(self): for invar in self.invariants(): if invar.precondition and not invar.precondition(self): continue invar.function(self)