Пример #1
0
    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)
Пример #2
0
    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)
Пример #3
0
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
Пример #4
0
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
Пример #5
0
 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)