def import_from_dict(data: Mapping[str, Any]) -> Statechart: data = data['statechart'] statechart = Statechart(name=data['name'], description=data.get('description', None), preamble=data.get('preamble', None)) states = [] # (StateMixin instance, parent name) transitions = [] # Transition instances # (State dict, parent name) data_to_consider = [ (data['root state'], None) ] # type: List[Tuple[Mapping[str, Any], Optional[str]]] while data_to_consider: state_data, parent_name = data_to_consider.pop() # Get state try: state = _import_state_from_dict(state_data) except StatechartError: raise except Exception as e: raise StatechartError('Unable to load given YAML') from e states.append((state, parent_name)) # Get substates if isinstance(state, CompoundState): for substate_data in state_data['states']: data_to_consider.append((substate_data, state.name)) elif isinstance(state, OrthogonalState): for substate_data in state_data['parallel states']: data_to_consider.append((substate_data, state.name)) # Get transition(s) for transition_data in state_data.get('transitions', []): try: transition = _import_transition_from_dict( state.name, transition_data) except StatechartError: raise except Exception as e: raise StatechartError('Unable to load given YAML') from e transitions.append(transition) # Register on statechart for state, parent in states: statechart.add_state(state, parent) for transition in transitions: statechart.add_transition(transition) return statechart
def import_from_dict(data: dict) -> Statechart: data = data['statechart'] statechart = Statechart(name=data['name'], description=data.get('description', None), preamble=data.get('preamble', None)) states = [] # (StateMixin instance, parent name) transitions = [] # Transition instances # (State dict, parent name) data_to_consider = [(data['root state'], None)] # type: List[Tuple[dict, Optional[str]]] while data_to_consider: state_data, parent_name = data_to_consider.pop() # Get state try: state = _import_state_from_dict(state_data) except StatechartError: raise except Exception as e: raise StatechartError('Unable to load given YAML') from e states.append((state, parent_name)) # Get substates if isinstance(state, CompoundState): for substate_data in state_data['states']: data_to_consider.append((substate_data, state.name)) elif isinstance(state, OrthogonalState): for substate_data in state_data['parallel states']: data_to_consider.append((substate_data, state.name)) # Get transition(s) for transition_data in state_data.get('transitions', []): try: transition = _import_transition_from_dict(state.name, transition_data) except StatechartError: raise except Exception as e: raise StatechartError('Unable to load given YAML') from e transitions.append(transition) # Register on statechart for state, parent in states: statechart.add_state(state, parent) for transition in transitions: statechart.add_transition(transition) return statechart
def import_from_amola(xmi_path: str, *, ignore_validation: bool=False) -> Statechart: """ Experiment support to import a statechart from AMOLA. :param xmi_path: path to an xmi file :param ignore_validation: Set to True to bypass statechart validation. :return: a statechart instance """ _, rset = load_metamodel() resource = rset.get_resource(URI(xmi_path)) model = resource.contents[0] # Create statechart if model.metadata: try: statechart = Statechart(**json.loads(model.metadata)) except json.JSONDecodeError as e: warnings.warn('Invalid model metadata. Expected a JSON field, got "{}".'.format(model.metadata)) else: statechart = Statechart(os.path.split(xmi_path)[-1]) # Create states state_klasses = { 'BASIC': BasicState, 'OR': CompoundState, 'AND': OrthogonalState, 'HISTORY': DeepHistoryState, 'SHALLOW_HISTORY': ShallowHistoryState, 'END': FinalState, } # State name def name(node): return node.name if node.name else 's'+str(id(node)) nodes = list(model.nodes) parents = {} while len(nodes) > 0: node = nodes.pop() state_klass = state_klasses.get(node.type, None) if state_klass: state = state_klass(name(node)) try: metadata = json.loads(node.metadata) if node.metadata else {} except json.JSONDecodeError: warnings.warn('Invalid metadata for node {}. Expected a JSON field, got "{}".'.format(node.name, node.metadata)) metadata = {} state.preconditions = metadata.get('preconditions', []) state.postconditions = metadata.get('postconditions', []) state.invariants = metadata.get('invariants', []) parents[name(node)] = name(node.Father) if node.Father else None statechart.add_state(state, parents.get(name(node), None)) nodes.extend(list(node.Children)) if node.actions: for action in node.actions.split('\n'): if action.startswith('entry /'): state.on_entry = action[7:].strip() elif action.startswith('exit /'): state.on_exit = action[6:].strip() elif len(action.strip()) > 0: # Static reactions should be encoded as internal transitions event, guard, action = import_TE(action) if event or guard or action: tr = Transition( source=name(node), target=None, event=event, guard=guard, action=action ) statechart.add_transition(tr) # Create transitions for transition in model.transitions: try: metadata = json.loads(transition.metadata) if transition.metadata else {} except json.JSONDecodeError: warnings.warn('Invalid metadata for transition {} -> {}. Expected a JSON field, got "{}".'.format( transition.source.name, transition.target.name, transition.metadata)) metadata = {} # Convert START state transition to state.initial if transition.source.type == 'START': statechart.state_for(name(transition.source.Father)).initial = name(transition.target) # Convert HISTORY/SHALLOW_HISTORY state transition to state.memory elif transition.source.type in ['HISTORY', 'SHALLOW_HISTORY']: statechart.state_for(name(transition.source)).memory = name(transition.target) else: # Parse Transition Expression event, guard, action = import_TE(transition.TE) tr = Transition( source=name(transition.source), target=None if metadata.get('internal', False) else name(transition.target), event=event, guard=guard, action=action, ) tr.preconditions = metadata.get('preconditions', []) tr.postconditions = metadata.get('postconditions', []) tr.invariants = metadata.get('invariants', []) statechart.add_transition(tr) # Validate, if required if not ignore_validation: statechart.validate() return statechart
class PropertiesTests(unittest.TestCase): def setUp(self): self.sequential_statechart = Statechart('tested_statechart') initial_state = CompoundState('initial_state', initial='a_state') a_state = BasicState('a_state') b_state = BasicState('b_state') self.sequential_statechart.add_state(initial_state, None) self.sequential_statechart.add_state(a_state, 'initial_state') self.sequential_statechart.add_state(b_state, 'initial_state') self.sequential_statechart.add_transition(Transition(source='a_state', target='b_state', event='event')) def generic_test(self, tested_statechart: Statechart, events: list, property: Condition, expected_success: bool, expected_failure: bool): from sismic.interpreter import log_trace tester_statechart = Statechart('tester_statechart') tester_statechart.add_state(OrthogonalState('parallel_state'), None) tester_statechart.add_state(CompoundState('testing_area', initial='property'), parent='parallel_state') tester_statechart.add_state(BasicState('success_state'), parent='testing_area') tester_statechart.add_state(BasicState('failure_state'), parent='testing_area') property.add_to(tester_statechart, id='property', parent_id='testing_area', status_id='parallel_state', success_id='success_state', failure_id='failure_state') tester_interpreter = Interpreter(tester_statechart) self.assertFalse('success_state' in tester_interpreter.configuration) self.assertFalse('failure_state' in tester_interpreter.configuration) tested_interpreter = Interpreter(tested_statechart) trace = log_trace(tested_interpreter) for event in events: tested_interpreter.queue(event) tested_interpreter.execute() story = teststory_from_trace(trace) story.tell(tester_interpreter) self.assertEqual(expected_success, 'success_state' in tester_interpreter.configuration) self.assertEqual(expected_failure, 'failure_state' in tester_interpreter.configuration) def test_condition_success(self): for (event, condition) in [ ('event', EnterState('b_state')), ('event', EnterState('foo', 'b_state', 'bar')), ('event', EnterAnyState()), ('event', ExitState('a_state')), ('event', ExitState('foo', 'a_state', 'bar')), ('event', ExitAnyState()), ('foo', ConsumeEvent('foo')), ('foo', ConsumeEvent('foo', 'bar')), ('foo', ConsumeAnyEvent()), ('foo', ConsumeAnyEventBut('bar')), ('foo', ConsumeAnyEventBut('bar', 'baz')), ('foo', ExecutionStart()), ('foo', ExecutionStop()), ('foo', StartStep()), ('foo', ExecutionStop()), ('event', TransitionProcess()), ('event', TransitionProcess(event='event')), ('event', TransitionProcess(source='a_state')), ('event', TransitionProcess(target='b_state')), ('event', Before(EnterState('a_state'), EnterState('b_state'))), ('event', Before(EnterState('a_state'), FalseCondition())), ('event', Before(EnterState('a_state'), UndeterminedCondition())), ('event', Before(EnterState('b_state'), FalseCondition())), ('event', Before(EnterState('b_state'), UndeterminedCondition())), ]: with self.subTest(condition=condition): self.generic_test(self.sequential_statechart, [Event(event)], condition, True, False) def test_condition_faillure(self): for (event, condition) in [ ('event', Before(EnterState('b_state'), EnterState('a_state'))), ]: with self.subTest(condition=condition): self.generic_test(self.sequential_statechart, [Event(event)], condition, False, True) def test_condition_undetermined(self): for (event, condition) in [ ('event', EnterState('foo')), ('event', ExitState('b_state')), ('foo', ExitAnyState()), ('bar', ConsumeEvent('foo')), ('foo', TransitionProcess()), ('event', TransitionProcess(event='foo')), ('event', TransitionProcess(source='foo')), ('event', TransitionProcess(target='foo')), ('event', TransitionProcess(event='')), ]: with self.subTest(condition=condition): self.generic_test(self.sequential_statechart, [Event(event)], condition, False, False) def test_check_guard_success(self): statechart = Statechart('statechart') statechart.add_state(OrthogonalState('parallel_state'), parent=None) initial_state = CompoundState('initial_state', initial='condition') statechart.add_state(initial_state, parent='parallel_state') statechart.add_state(BasicState('success'), parent='initial_state') statechart.add_state(BasicState('failure'), parent='initial_state') CheckGuard('x == 1').add_to(statechart=statechart, id='condition', parent_id='initial_state', status_id='parallel_state', success_id='success', failure_id='failure') interpreter = Interpreter(statechart) interpreter.context['x'] = 1 interpreter.execute() self.assertTrue('success' in interpreter.configuration) self.assertFalse('failure' in interpreter.configuration) def test_check_guard_failure(self): statechart = Statechart('statechart') statechart.add_state(OrthogonalState('parallel_state'), parent=None) initial_state = CompoundState('initial_state', initial='condition') statechart.add_state(initial_state, parent='parallel_state') statechart.add_state(BasicState('success'), parent='initial_state') statechart.add_state(BasicState('failure'), parent='initial_state') CheckGuard('x == 1').add_to(statechart=statechart, id='condition', parent_id='initial_state', status_id='parallel_state', success_id='success', failure_id='failure') interpreter = Interpreter(statechart) interpreter.context['x'] = 42 interpreter.execute() self.assertFalse('success' in interpreter.configuration) self.assertTrue('failure' in interpreter.configuration) def test_consume_any_event_but_wrong(self): self.generic_test(self.sequential_statechart, [], ConsumeAnyEventBut('foo'), False, False) def test_transition_process_right_eventless(self): self.sequential_statechart.remove_transition(Transition(source='a_state', target='b_state', event='event')) self.sequential_statechart.add_transition(Transition(source='a_state', target='b_state')) self.generic_test(self.sequential_statechart, [Event('event')], TransitionProcess(event=''), True, False) def test_active_state_right(self): self.sequential_statechart.remove_transition(Transition(source='a_state', target='b_state', event='event')) self.generic_test(self.sequential_statechart, [Event('event')], Then(ConsumeEvent('event'), ActiveState(state='a_state')), True, False) def test_active_state_wrong(self): self.sequential_statechart.remove_transition(Transition(source='a_state', target='b_state', event='event')) self.generic_test(self.sequential_statechart, [Event('event')], Then(ConsumeEvent('event'), ActiveState(state='b_state')), False, True) def test_inactive_state_right(self): self.sequential_statechart.remove_transition(Transition(source='a_state', target='b_state', event='event')) self.generic_test(self.sequential_statechart, [Event('event')], Then(ConsumeEvent('event'), InactiveState(state='b_state')), True, False) def test_inactive_state_wrong(self): self.sequential_statechart.remove_transition(Transition(source='a_state', target='b_state', event='event')) self.generic_test(self.sequential_statechart, [Event('event')], Then(ConsumeEvent('event'), InactiveState(state='a_state')), False, True)