from dataclasses import dataclass import unittest from snanosm.mealy import Machine, TransitionInputEnum, State, Transition # Any hashable and equatable object should suffice @dataclass(frozen=True) class TestTransitionObject: attribute: str class TestMachine(unittest.TestCase): def test_init(self): m = Machine() self.assertIsNotNone(m) # ADD STATE def test_add_state_two_start_states(self): m = Machine() m.add_state("A", True, False) with self.assertRaises(ValueError): m.add_state("B", True, False) def test_add_two_end_states(self): m = Machine() m.add_state("A", True, False) m.add_state("B", False, True) m.add_state("C", False, True) def test_add_state_with_reserved_name(self): m = Machine() with self.assertRaises(ValueError): m.add_state("#INVALID_STATE_NAME", True, False) def test_add_state_with_duplicate_state_name(self): m = Machine() m.add_state("A", True, False) with self.assertRaises(ValueError): m.add_state("A", False, True) # ADD TRANSITION def test_add_transition(self): m = Machine() m.add_state("A", True, False) m.add_state("B", False, True) m.add_transition("X", "A", "B", lambda context: print(context)) def test_add_transition_non_existent_origin_state(self): m = Machine() m.add_state("A", True, False) m.add_state("B", False, True) with self.assertRaises(ValueError): m.add_transition("X", "C", "B", lambda context: print(context)) def test_add_transition_non_existent_destination_state(self): m = Machine() m.add_state("A", True, False) m.add_state("B", False, True) with self.assertRaises(ValueError): m.add_transition("X", "A", "C", lambda context: print(context)) def test_add_special_transition(self): m = Machine() m.add_state("A", True, False) m.add_state("B", False, True) m.add_transition(TransitionInputEnum.MATCH_REST, "A", "B", lambda context: print(context)) def test_add_several_transitions_with_same_origin(self): m = Machine() m.add_state("A", True, False) m.add_state("B", False, True) m.add_transition("X", "A", "B", lambda context: print("1")) m.add_transition("Y", "A", "B", lambda context: print("2")) def test_add_duplicate_transition(self): m = Machine() m.add_state("A", True, False) m.add_state("B", False, True) m.add_transition("X", "A", "B", lambda context: print("1")) with self.assertRaises(ValueError): m.add_transition("X", "A", "B", lambda context: print("2")) # PROCESS INPUT def test_process_input(self): m = Machine() m.add_state("A", True, False) m.add_state("B", False, False) m.add_state("C", False, True) m.add_transition("X", "A", "B", lambda context: print("TEST_PROCESS_INPUT: Transition 1")) m.add_transition("X", "B", "C", lambda context: print("TEST_PROCESS_INPUT: Transition 2")) m.process_input("X") m.process_input("X") current_state = m.get_current_state() self.assertIsNotNone(current_state) assert current_state is not None self.assertEqual(current_state.get_name(), "C") def test_process_input_no_start_state(self): m = Machine() m.add_state("A", False, False) m.add_state("B", False, False) m.add_transition("X", "A", "B", lambda context: print("TEST_PROCESS_INPUT_NO_START_DATE: Transition")) with self.assertRaises(ValueError): m.process_input("X") def test_process_input_state_without_transitions(self): m = Machine() m.add_state("A", True, False) m.add_state("B", False, True) m.add_transition("X", "A", "B", lambda context: print("TEST_PROCESS_INPUT_WITHOUT_TRANSITIONS: Transition")) m.process_input("X") current_state = m.get_current_state() self.assertIsNotNone(current_state) assert current_state is not None self.assertEqual(current_state.get_name(), "B") with self.assertRaises(ValueError): m.process_input("X") def test_process_input_catch_all_transition(self): m = Machine() m.add_state("A", True, False) m.add_state("B", False, True) m.add_transition(TransitionInputEnum.MATCH_REST, "A", "B", lambda context: print("A -> B")) m.add_transition(TransitionInputEnum.MATCH_REST, "B", "A", lambda context: print("B -> A")) m.process_input("X") current_state = m.get_current_state() self.assertIsNotNone(current_state) assert current_state is not None self.assertEqual(current_state.get_name(), "B") m.process_input(1) current_state = m.get_current_state() self.assertIsNotNone(current_state) assert current_state is not None self.assertEqual(current_state.get_name(), "A") m.process_input(TestTransitionObject("X")) current_state = m.get_current_state() self.assertIsNotNone(current_state) assert current_state is not None self.assertEqual(current_state.get_name(), "B") def test_process_input_with_objects(self): m = Machine() m.add_state("A", True, False) m.add_state("B", False, False) m.add_state("C", False, True) m.add_transition(TestTransitionObject("TEST_1"), "A", "B", lambda context: print("A -> B")) m.add_transition(TestTransitionObject("TEST_2"), "B", "C", lambda context: print("B -> C")) m.process_input(TestTransitionObject("TEST_1")) current_state = m.get_current_state() self.assertIsNotNone(current_state) assert current_state is not None self.assertEqual(current_state.get_name(), "B") m.process_input(TestTransitionObject("TEST_2")) current_state = m.get_current_state() self.assertIsNotNone(current_state) assert current_state is not None self.assertEqual(current_state.get_name(), "C") def test_process_input_invalid_transition(self): m = Machine() m.add_state("A", True, False) m.add_state("B", False, True) m.add_transition("X", "A", "B", lambda context: print("A -> B")) with self.assertRaises(ValueError): m.process_input("Y") # GET CURRENT STATE def test_get_current_state_machine_not_started(self): m = Machine() state = m.get_current_state() self.assertIsNone(state) def test_get_current_state_machine_started(self): m = Machine() m.add_state("A", True, False) m.add_state("B", False, True) m.add_transition("X", "A", "B", lambda context: print("A -> B")) m.process_input("X") current_state = m.get_current_state() self.assertIsNotNone(current_state) assert current_state is not None self.assertEqual(current_state.get_name(), "B") # RESET def test_reset(self): m = Machine() m.add_state("C", True, False) m.add_state("D", False, True) m.add_transition("X", "C", "D", lambda context: print("A -> B")) m.process_input("X") current_state = m.get_current_state() self.assertIsNotNone(current_state) assert current_state is not None self.assertEqual(current_state.get_name(), "D") m.reset() current_state = m.get_current_state() self.assertIsNotNone(current_state) assert current_state is not None self.assertEqual(current_state.get_name(), "C") def test_reset_no_start_state(self): m = Machine() m.add_state("A", False, False) with self.assertRaises(ValueError): m.reset() # IS_IN_FINAL_STATE def test_is_in_final_state(self): m = Machine() m.add_state("A", True, False) m.add_state("B", False, True) m.add_transition("X", "A", "B", lambda context: print("A -> B")) m.process_input("X") self.assertTrue(m.is_in_final_state()) m.reset() self.assertFalse(m.is_in_final_state()) def test_is_in_final_state_not_started(self): m = Machine() self.assertFalse(m.is_in_final_state()) # STATE def test_state_str(self): s = State("A") t = str(s) self.assertEqual(t, "[State A]") # TRANSITION def test_transition_str(self): t = Transition("TEST_1", "A", "B", lambda context: print("A -> B")) self.assertEqual(str(t), "[Transition (A, B, TEST_1)]") # MACHINE def test_machine_str(self): m = Machine() m.add_state("A", True, False) m.add_state("B", False, False) m.add_state("C", False, True) m.add_transition("TEST_1", "A", "B", lambda context: print("A -> B")) m.add_transition("TEST_2", "B", "C", lambda context: print("B -> C")) result = "=MACHINE=\n\tSTATES\n\t\t[State A]\n\t\t[State B]\n\t\t[State C]\n\tTRANSITIONS\n\t\t[Transition (A, B, TEST_1)]\n\t\t[Transition (B, C, TEST_2)]" self.assertEqual(str(m), result)