Files
snanosm/tests/test_mealy.py
T

249 lines
8.9 KiB
Python
Raw Normal View History

2026-07-15 16:47:03 +03:00
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)
# GET STATE
def test_get_states(self):
m = Machine()
m.add_state("A", True, False)
state = m.get_state("A")
self.assertIsNotNone(state)
assert state is not None
self.assertEqual(state.get_name(), "A")
# 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("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")
m.reset()
current_state = m.get_current_state()
self.assertIsNotNone(current_state)
assert current_state is not None
self.assertEqual(current_state.get_name(), "A")
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)]")
2026-07-15 17:13:07 +03:00
# 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)
2026-07-15 16:47:03 +03:00