def test_root_already_defined(self): root = CompoundState('root', 'a') sc = Statechart('test') sc.add_state(root, None) with pytest.raises(StatechartError) as e: sc.add_state(root, None) assert 'already exists!' in str(e.value)
def test_name_collision(self): root = CompoundState('root', 'a') sc = Statechart('test') sc.add_state(root, None) s1 = BasicState('a') s2 = BasicState('a') sc.add_state(s1, parent='root') with pytest.raises(StatechartError) as e: sc.add_state(s2, parent='root') assert 'already exists!' in str(e.value)
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_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
def test_name_is_none(self): sc = Statechart('test') state = BasicState(name=None) with pytest.raises(StatechartError) as e: sc.add_state(state, None) assert 'must have a name' in str(e.value)