diff --git a/yasmin/tests/python/test_state_machine.py b/yasmin/tests/python/test_state_machine.py index 5cec5ab..c40a56d 100644 --- a/yasmin/tests/python/test_state_machine.py +++ b/yasmin/tests/python/test_state_machine.py @@ -102,7 +102,7 @@ def test_add_state_with_wrong_outcome(self): ) self.assertEqual( str(context.exception), - "State 'FOO1' references unregistered outcomes: 'outcome9', available outcomes are: ['outcome1', 'outcome2']", + "State 'FOO1' references unregistered outcomes 'outcome9', available outcomes are ['outcome1', 'outcome2']", ) def test_add_wrong_source_transition(self): @@ -131,34 +131,101 @@ def test_add_wrong_target_transition(self): str(context.exception), "Transitions with empty target in state 'FOO1'" ) - def test_validate_state_machine(self): + def test_validate_state_machine_outcome_from_fsm_not_used(self): sm_1 = StateMachine(outcomes=["outcome4"]) sm_2 = StateMachine(outcomes=["outcome4", "outcome5"]) sm_1.add_state("FSM", sm_2) + sm_2.add_state( + "FOO", + FooState(), + transitions={ + "outcome1": "outcome4", + "outcome2": "outcome4", + }, + ) + with self.assertRaises(Exception) as context: + sm_1.validate() + self.assertEqual( + str(context.exception), + "State 'FSM' outcome 'outcome5' not registered in transitions", + ) + + def test_validate_state_machine_outcome_from_state_not_used(self): + + sm_1 = StateMachine(outcomes=["outcome4"]) + + sm_2 = StateMachine(outcomes=["outcome4"]) + sm_1.add_state("FSM", sm_2) + + sm_2.add_state( + "FOO", + FooState(), + transitions={ + "outcome1": "outcome4", + }, + ) + with self.assertRaises(Exception) as context: + sm_1.validate() + self.assertEqual( + str(context.exception), + "State 'FOO' outcome 'outcome2' not registered in transitions", + ) + + def test_validate_state_machine_fsm_outcome_not_used(self): + + sm_1 = StateMachine(outcomes=["outcome4"]) + + sm_2 = StateMachine(outcomes=["outcome4", "outcome5"]) + sm_1.add_state( + "FSM", + sm_2, + transitions={ + "outcome5": "outcome4", + }, + ) + + sm_2.add_state( + "FOO", + FooState(), + transitions={ + "outcome1": "outcome4", + "outcome2": "outcome4", + }, + ) + with self.assertRaises(Exception) as context: + sm_1.validate() + self.assertEqual( + str(context.exception), + "Target outcome 'outcome5' not registered in transitions", + ) + + def test_validate_state_machine_wrong_state(self): + + sm_1 = StateMachine(outcomes=["outcome4"]) + + sm_2 = StateMachine(outcomes=["outcome4"]) + sm_1.add_state( + "FSM", + sm_2, + transitions={ + "outcome4": "outcome4", + }, + ) + sm_2.add_state( "FOO", FooState(), transitions={ "outcome1": "BAR", + "outcome2": "outcome4", }, ) with self.assertRaises(Exception) as context: sm_1.validate() self.assertEqual( str(context.exception), - ( - f"{'*' * 100}\nState machine failed validation check:" - "\n\tState 'FSM' outcome 'outcome5' not registered in transitions" - f"\n\tState machine 'FSM' failed validation check\n{'*' * 100}\n" - "State machine failed validation check:" - "\n\tState 'FOO' outcome 'outcome2' not registered in transitions" - "\n\tTarget outcome 'outcome4' not registered in transitions" - "\n\tTarget outcome 'outcome5' not registered in transitions" - "\n\tState machine outcome 'BAR' not registered as outcome neither state" - f"\n\n\tAvailable states: ['FOO']\n{'*' * 100}" - f"\n\n\tAvailable states: ['FSM']\n{'*' * 100}" - ), + "State machine outcome 'BAR' not registered as outcome neither state", ) diff --git a/yasmin/yasmin/state_machine.py b/yasmin/yasmin/state_machine.py index 2399507..66cc7ba 100644 --- a/yasmin/yasmin/state_machine.py +++ b/yasmin/yasmin/state_machine.py @@ -54,7 +54,7 @@ def add_state( if key not in state.get_outcomes(): raise Exception( - f"State '{name}' references unregistered outcomes: '{key}', available outcomes are: {state.get_outcomes()}" + f"State '{name}' references unregistered outcomes '{key}', available outcomes are {state.get_outcomes()}" ) self._states[name] = {"state": state, "transitions": transitions} @@ -74,14 +74,15 @@ def cancel_state(self) -> None: if self.__current_state: self._states[self.__current_state]["state"].cancel_state() - def validate(self, raise_exception: bool = True) -> str: - errors = "" + def validate(self) -> None: # check initial state if self._start_state is None: - errors += "\n\tNo initial state set." + raise Exception("No initial state set") elif self._start_state not in self._states: - errors += f"\n\tInitial state label: '{self._start_state}' is not in the state machine." + raise Exception( + f"Initial state label: '{self._start_state}' is not in the state machine" + ) terminal_outcomes = [] @@ -96,7 +97,9 @@ def validate(self, raise_exception: bool = True) -> str: # check if all state outcomes are in transitions for o in outcomes: if o not in set(list(transitions.keys()) + self.get_outcomes()): - errors += f"\n\tState '{state_name}' outcome '{o}' not registered in transitions" + raise Exception( + f"State '{state_name}' outcome '{o}' not registered in transitions" + ) # state outcomes that are in state machines out do not need transitions elif o in self.get_outcomes(): @@ -104,9 +107,7 @@ def validate(self, raise_exception: bool = True) -> str: # if sate is a state machine, validate it if isinstance(state, StateMachine): - aux_errors = state.validate(False) - if aux_errors: - errors += f"\n\tState machine '{state_name}' failed validation check\n{aux_errors}" + state.validate() # add terminal outcomes terminal_outcomes.extend([transitions[key] for key in transitions]) @@ -117,20 +118,14 @@ def validate(self, raise_exception: bool = True) -> str: # check if all state machine outcomes are in the terminal outcomes for o in self.get_outcomes(): if o not in terminal_outcomes: - errors += f"\n\tTarget outcome '{o}' not registered in transitions" + raise Exception(f"Target outcome '{o}' not registered in transitions") # check if all terminal outcomes are states or state machine outcomes for o in terminal_outcomes: if o not in set(list(self._states.keys()) + self.get_outcomes()): - errors += f"\n\tState machine outcome '{o}' not registered as outcome neither state" - - if errors: - errors = f"{'*' * 100}\nState machine failed validation check:{errors}\n\n\tAvailable states: {list(self._states.keys())}\n{'*' * 100}" - - if raise_exception: - raise Exception(errors) - - return errors + raise Exception( + f"State machine outcome '{o}' not registered as outcome neither state" + ) def execute(self, blackboard: Blackboard) -> str: