239 lines
8.6 KiB
Python
239 lines
8.6 KiB
Python
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()
|
|
|
|
# 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)
|
|
|
|
|