class FysomStateTests(unittest.TestCase): def setUp(self): self.fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ] }) def test_is_state_should_succeed_for_initial_state(self): self.assertTrue(self.fsm.isstate('green')) def test_identity_transition_should_not_be_allowed_by_default(self): self.assertFalse(self.fsm.can('clear')) self.assertTrue(self.fsm.cannot('clear')) def test_configured_transition_should_work(self): self.assertTrue(self.fsm.can('warn')) def test_transition_should_change_state(self): self.fsm.warn() self.assertTrue(self.fsm.isstate('yellow')) def test_should_raise_exception_when_state_transition_is_not_allowed(self): self.assertRaises(FysomError, self.fsm.panic) self.assertRaises(FysomError, self.fsm.calm) self.assertRaises(FysomError, self.fsm.clear)
def do_stem(self, word): fsm = Fysom(initial='start', events=self.events) i = len(word) - 1 j = len(word) while (True): if i <= 0: break v = word[i:j] # print v res = fsm.can(v) if (res): if v == 'i' and fsm.can(word[i - 1:j]): i = i - 1 continue fsm.trigger(v) if fsm.current == 'h': if word[i - 1:i] == 'i': i = i - 1 # skip i if word[i - 1:i] == 'n': # ning qushimchasi fsm.current = 'start' continue elif fsm.current == 'b': fsm.current = 'start' j = i i = i - 1 return word[:j]
def do_stem(self, word): fsm = Fysom(initial='start', events=self.events) # FIXME: uncomment below and make sanitize functions support # both Python 2 and 3 versions # word = WordProcessor.sanitize(word) i = len(word) - 1 j = len(word) while (True): if i <= 0: break v = word[i:j] # print v res = fsm.can(v) if (res): if v == 'i' and fsm.can(word[i - 1:j]): i = i - 1 continue fsm.trigger(v) if fsm.current == 'h': if word[i - 1:i] == 'i': i = i - 1 # skip i if word[i - 1:i] == 'n': # ning qushimchasi fsm.current = 'start' continue elif fsm.current == 'b': fsm.current = 'start' j = i i = i - 1 return word[:j]
def do_stem(self, word): fsm = Fysom(initial='start', events=self.events) # FIXME: uncomment below and make sanitize functions support # both Python 2 and 3 versions # word = WordProcessor.sanitize(word) i = len(word) - 1 j = len(word) while(True): if i <= 0: break v = word[i:j] # print v res = fsm.can(v) if (res): if v == 'i' and fsm.can(word[i-1:j]): i = i - 1 continue fsm.trigger(v) if fsm.current == 'h': if word[i-1:i] == 'i': i = i - 1 # skip i if word[i-1:i] == 'n': # ning qushimchasi fsm.current = 'start' continue elif fsm.current == 'b': fsm.current = 'start' j = i i = i - 1 return word[:j]
class FysomStateTests(unittest.TestCase): def setUp(self): self.fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'}, {'name': 'warm', 'src': 'green', 'dst': 'blue'} ] }) def test_is_state_should_succeed_for_initial_state(self): self.assertTrue(self.fsm.isstate('green')) def test_identity_transition_should_not_be_allowed_by_default(self): self.assertFalse(self.fsm.can('clear')) self.assertTrue(self.fsm.cannot('clear')) def test_configured_transition_should_work(self): self.assertTrue(self.fsm.can('warn')) def test_transition_should_change_state(self): self.fsm.warn() self.assertTrue(self.fsm.isstate('yellow')) def test_should_raise_exception_when_state_transition_is_not_allowed(self): self.assertRaises(FysomError, self.fsm.panic) self.assertRaises(FysomError, self.fsm.calm) self.assertRaises(FysomError, self.fsm.clear) def test_event_handler_has_name_and_docstring(self): self.assertEqual(self.fsm.warm.__name__, "warm", "Event handlers do not have appropriate name.") self.assertNotEqual(self.fsm.warm.__name__, None, "Docstring for event handler is None!") def test_trigger_should_trigger_the_event_handler(self): self.assertEqual(self.fsm.current, "green", "The initial state isn't the expected state.") self.fsm.trigger("warm") make_callable = lambda: self.fsm.trigger("unknowevent") self.assertRaises(FysomError, make_callable) self.assertEqual(self.fsm.current, "blue", "The initial state isn't the expected state.")
class FysomStateTests(unittest.TestCase): def setUp(self): self.fsm = Fysom({ 'initial': 'green', 'events': [{ 'name': 'warn', 'src': 'green', 'dst': 'yellow' }, { 'name': 'panic', 'src': 'yellow', 'dst': 'red' }, { 'name': 'calm', 'src': 'red', 'dst': 'yellow' }, { 'name': 'clear', 'src': 'yellow', 'dst': 'green' }] }) def test_is_state_should_succeed_for_initial_state(self): self.assertTrue(self.fsm.isstate('green')) def test_identity_transition_should_not_be_allowed_by_default(self): self.assertFalse(self.fsm.can('clear')) self.assertTrue(self.fsm.cannot('clear')) def test_configured_transition_should_work(self): self.assertTrue(self.fsm.can('warn')) def test_transition_should_change_state(self): self.fsm.warn() self.assertTrue(self.fsm.isstate('yellow')) def test_should_raise_exception_when_state_transition_is_not_allowed(self): self.assertRaises(FysomError, self.fsm.panic) self.assertRaises(FysomError, self.fsm.calm) self.assertRaises(FysomError, self.fsm.clear)
class StatesTestCase(unittest.TestCase): def setUp(self): self.fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ] }) def test_invalid_state(self): self.assertRaises(AttributeError, getattr, self.fsm, 'wtfbbq') def test_state_conditions(self): """ This will check for example, if state is in clear, will be able to emit warn and will not be able to emit panic, calm or clear again. """ self.assertTrue(self.fsm.isstate('green')) self.assertTrue(self.fsm.can('warn')) self.assertFalse(self.fsm.can('panic')) self.assertTrue(self.fsm.cannot('panic')) self.assertFalse(self.fsm.can('clam')) self.assertFalse(self.fsm.can('clear')) def test_state_changing(self): self.assertTrue(self.fsm.isstate('green')) self.fsm.warn() self.assertTrue(self.fsm.isstate('yellow')) def test_state_cannot_be_called_twice(self): self.fsm.warn() self.assertTrue(self.fsm.isstate('yellow')) self.assertRaises(FysomError, self.fsm.warn) def test_auto_next_state(self): self.assertEqual('green', self.fsm.current) self.assertEqual('yellow', self.fsm.next()) self.assertEqual('yellow', self.fsm.current) def test_multiple_possibilities_for_next(self): fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'panic', 'src': 'green', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ] }) self.assertRaises(MultiplePossibilitesFound, fsm.next) def test_no_possibility_for_next(self): fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ] }) self.assertRaises(NoPossibilityFound, fsm.next) def test_goto(self): fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'panic', 'src': 'green', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ] }) self.assertRaises(MultiplePossibilitesFound, fsm.next) self.assertEqual('red', fsm.goto('panic')) def test_no_possibility_for_goto(self): fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'panic', 'src': 'green', 'dst': 'red'}, # when call panic from green, we go to red, from red we # should go to yellow, but it is commented and we should # have a exception # {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ] }) self.assertRaises(MultiplePossibilitesFound, fsm.next) self.assertEqual('red', fsm.goto('panic')) self.assertRaises(NoPossibilityFound, fsm.goto, 'calm')
class FysomStateTests(unittest.TestCase): def setUp(self): self.fsm = Fysom({ 'initial': 'green', 'events': [{ 'name': 'warn', 'src': 'green', 'dst': 'yellow' }, { 'name': 'panic', 'src': 'yellow', 'dst': 'red' }, { 'name': 'calm', 'src': 'red', 'dst': 'yellow' }, { 'name': 'clear', 'src': 'yellow', 'dst': 'green' }, { 'name': 'warm', 'src': 'green', 'dst': 'blue' }] }) def test_is_state_should_succeed_for_initial_state(self): self.assertTrue(self.fsm.isstate('green')) def test_identity_transition_should_not_be_allowed_by_default(self): self.assertFalse(self.fsm.can('clear')) self.assertTrue(self.fsm.cannot('clear')) def test_configured_transition_should_work(self): self.assertTrue(self.fsm.can('warn')) def test_transition_should_change_state(self): self.fsm.warn() self.assertTrue(self.fsm.isstate('yellow')) def test_should_raise_exception_when_state_transition_is_not_allowed(self): self.assertRaises(FysomError, self.fsm.panic) self.assertRaises(FysomError, self.fsm.calm) self.assertRaises(FysomError, self.fsm.clear) def test_event_handler_has_name_and_docstring(self): self.assertEqual(self.fsm.warm.__name__, "warm", "Event handlers do not have appropriate name.") self.assertNotEqual(self.fsm.warm.__name__, None, "Docstring for event handler is None!") def test_trigger_should_trigger_the_event_handler(self): self.assertEqual(self.fsm.current, "green", "The initial state isn't the expected state.") self.fsm.trigger("warm") self.assertRaises(FysomError, self.fsm.trigger, "unknown_event") self.assertEqual(self.fsm.current, "blue", "The initial state isn't the expected state.") def test_trigger_should_trigger_the_event_handler_with_args(self): self.assertEqual(self.fsm.current, "green", "The initial state isn't the expected state.") def onblue(event): self.assertEqual(event.args, ("any-positional-argument", )) self.fsm.onblue = onblue self.fsm.trigger("warm", "any-positional-argument") self.assertEqual(self.fsm.current, "blue", "The initial state isn't the expected state.") def test_trigger_should_trigger_the_event_handler_with_kwargs(self): self.assertEqual(self.fsm.current, "green", "The initial state isn't the expected state.") def onblue(event): self.assertEqual(event.keyword_argument, "any-value") self.fsm.onblue = onblue self.fsm.trigger("warm", keyword_argument="any-value") self.assertEqual(self.fsm.current, "blue", "The initial state isn't the expected state.")
#!/usr/bin/python from fysom import Fysom print "Experimenting finite state machine package." fsm = Fysom({'initial': 'green', 'events': [ { 'name': 'warn', 'src': 'green', 'dst': 'yellow' }, { 'name': 'panic', 'src': 'yellow', 'dst': 'red' }, { 'name': 'calm', 'src': 'red', 'dst': 'yellow' }, { 'name': 'clear', 'src': 'yellow', 'dst': 'green' } ]}) print "Current state = " + fsm.current print "Is in state 'red? = %r" % fsm.isstate('red') print "Is in state 'green? = %r" % fsm.isstate('green') print "Can fire event %s from state %s? = %r" % ('warn', fsm.current, fsm.can('warn')) print "Can fire event %s from state %s? = %r" % ('panic', fsm.current, fsm.can('panic')) print "Can fire event %s from state %s? = %r" % ('clear', fsm.current, fsm.can('clear')) print "" print "Firing event warn" fsm.warn() print "Current state = " + fsm.current print "Is in state 'red? = %r" % fsm.isstate('red') print "Is in state 'green? = %r" % fsm.isstate('green') print "Can fire event %s from state %s? = %r" % ('warn', fsm.current, fsm.can('warn')) print "Can fire event %s from state %s? = %r" % ('panic', fsm.current, fsm.can('panic')) print "Can fire event %s from state %s? = %r" % ('clear', fsm.current, fsm.can('clear'))
class Manager(object): INTF = "cmc_eth0" DHCP_SOCK_ADDRESS = 'ipc:///tmp/zmq-dhcp.ipc' TPC_SOCK_ADDRESS = 'ipc:///tmp/zmq-tpc.ipc' RCP_SOCK_ADDRESS = 'ipc:///tmp/zmq-rcp.ipc' HAL_SOCK_ADDRESS = 'ipc:///tmp/zmq-hal.ipc' EXAMPLE_PROC_SOCK_ADDRESS = 'ipc:///tmp/zmq-example.ipc' IF_UP_TIMEOUT = 60 # If process should respond in exponential backoff timer, this is safety # catch for cases, when something goes wrong BACKOFF_TIMEOUT = 600 dhcp_data_path = ['oper', 'DhcpData'] hw_version_path = ['oper', 'HwVersion'] hw_version = "OPENWRT v1" __metaclass__ = AddLoggerToClass def __init__(self): self.dhcpv4_process = None self.dhcpv6_process = None self.sfd_stream = None self.db = RPD_DB() self.db_adapter = CfgDbAdapter(self.db) self.dhcp_data = t_DhcpData() self.disp = Dispatcher() self.signal_mask = self.create_signal_mask() self.dhcp_sock = None self.dhcp_timer = None self.processes = {} ProcessInfo.dispatcher = self.disp self.processes['dhcpv6'] = ProcessInfo( [ "odhcp6c", # Args - start "-s", "/lib/netifd/dhcpv6.script", "-P", "0", # Request IPv6 Prefix = auto "-t", "256", # Random backoff <1, 256> "-v", # Verbose "-I", self.DHCP_SOCK_ADDRESS ], # Args - end self.DHCP_SOCK_ADDRESS, # IPC address self.BACKOFF_TIMEOUT, # Timeout in seconds self._dhcp_no_lease # Timeout callback ) self.processes['dhcpv4'] = ProcessInfo( [ "udhcpc", # Args - start "-p", # Create PID file "/var/run/udhcpc-" + self.INTF + ".pid", "-f", "-t", "8", # Random backoff <1, 256> "-i", self.INTF, "-C", # Don't send MAC address as client-id "-B", # Enable broadcast "-S", # Enable logging to syslog "-n", # Exit if lease is not obtained "-I", self.DHCP_SOCK_ADDRESS ], # Args - end self.DHCP_SOCK_ADDRESS, # IPC address self.BACKOFF_TIMEOUT, # Timeout in seconds self._dhcp_no_lease # Timeout callback ) self.processes['rcp'] = ProcessInfo( [ "python", # Args - start "-m", "rpd.rcp.rcp_process", "--ipc-address", self.RCP_SOCK_ADDRESS ], # Args - end self.RCP_SOCK_ADDRESS # IPC address ) confFile = '/etc/config/hal.conf' self.processes['hal'] = ProcessInfo( ("python -m rpd.hal.src.HalMain --conf=" + confFile).split(" "), "") self.processes['tpc'] = ProcessInfo( [ "python", # Args - start "-m", "rpd.tpc", "--ipc-address", self.TPC_SOCK_ADDRESS ], # Args - end self.TPC_SOCK_ADDRESS, # IPC address self.BACKOFF_TIMEOUT, # Timeout in seconds self.reboot # Timeout callback ) self.processes['example'] = ProcessInfo( [ "python", # Args - start "-m", "rpd.example", "--ipc-address", self.EXAMPLE_PROC_SOCK_ADDRESS ], # Args - end self.EXAMPLE_PROC_SOCK_ADDRESS, # IPC address 60, # Timeout in seconds self.reboot # Timeout callback ) self.processes['example'].start(self.example_msg_cb) # Source of subTLV codes - section 6.4.1 of # http://www.cablelabs.com/wp-content/uploads/specdocs/CM-SP-R-PHY-I01_150615.pdf rpd_ident = ['cfg', 'RpdCapabilities', 'RpdIdentification'] self.dhcp_args_mapping = { '0x02': rpd_ident + ['DeviceDescription'], '0x04': rpd_ident + ['SerialNumber'], '0x05': Manager.hw_version_path, '0x06': rpd_ident + ['CurrentSwVersion'], '0x07': rpd_ident + ['BootRomVersion'], '0x08': "".join(SysTools.get_mac_address( self.INTF).split(':')[0:3]), # vendor ID '0x09': rpd_ident + ['ModelNumber'], '0x0A': rpd_ident + ['VendorName'] } # Fill device information to DB, if not loaded mac_addr_str = rpd_ident + ['DeviceMacAddress'] if self.db_adapter.get_leaf(mac_addr_str) is None: # TODO negative case handling self.db_adapter.set_leaf(mac_addr_str, SysTools.get_mac_address(self.INTF), True) hostname_str = rpd_ident + ['DeviceAlias'] if self.db_adapter.get_leaf(hostname_str) is None: # TODO negative case handling self.db_adapter.set_leaf(hostname_str, SysTools.get_host_name(), True) # TODO get from HW if self.db_adapter.get_leaf(Manager.hw_version_path) is None: # TODO negative case handling self.db_adapter.set_leaf(Manager.hw_version_path, Manager.hw_version, True) self.fsm = Fysom({ 'initial': { 'state': 'init' }, 'events': [{ 'name': 'init_done', 'src': 'init', 'dst': 'if_up_waiting' }, { 'name': 'if_is_up', 'src': 'if_up_waiting', 'dst': 'dhcpv6_waiting' }, { 'name': 'dhcpv6_failed', 'src': 'dhcpv6_waiting', 'dst': 'dhcpv4_waiting' }, { 'name': 'dhcp_ack', 'src': [ 'dhcpv6_waiting', 'dhcpv4_waiting', 'time_waiting', 'log_waiting', 'gcp_started' ], 'dst': 'time_waiting' }, { 'name': 'time_cfged', 'src': 'time_waiting', 'dst': 'log_waiting' }, { 'name': 'log_done', 'src': 'log_waiting', 'dst': 'gcp_started' }, { 'name': 'fatal_failure', 'src': [ 'if_up_waiting', 'dhcpv6_waiting', 'dhcpv4_waiting', 'time_waiting', 'gcp_started' ], 'dst': 'reboot' }], 'callbacks': { 'onchangestate': self._onchangestate, 'onif_is_up': self._on_iface_is_up, 'ondhcp_ack': self._ondhcp_ack, 'ontime_cfged': self._ontime_cfged, 'onlog_done': self._onlog_done, 'onfatal_failure': self.reboot, } }) def delete_dhcp_data(self): """Delete DHCP data structure from DB and also clear cached copy of it. :return: """ # TODO negative case handling self.db_adapter.del_leaf(Manager.dhcp_data_path) def store_dhcp_data(self): """Save updated cached copy of DHCP data to DB. This must be called after each set operation to this cached structure (to keep it synchronized). :return: """ # TODO negative case handling - clear self.DhcpData self.db_adapter.set_leaf(Manager.dhcp_data_path, self.dhcp_data, True) def _dhcp_timeout_cb(self, _): """DHCP process haven't responded in limited time (backoff timer + extra time), so probably something wrong happened (DHCP process crashed, was killed, stuck in a loop, ...) :return: """ self.logger.warn("DHCP timer expired") self._dhcp_no_lease() def _dhcp_data_ready(self): """Received new DHCP data, do necessary cleanup if needed (start/update) (old GCP sessions must be closed). Keep remote logging enabled, so we don't loose syslog messages in case of DHCP update. :return: """ if not self.fsm.can('dhcp_ack'): raise ValueError("Wrong state '%s' for dhcp_ack event", self.fsm.current) self.fsm.dhcp_ack() def _dhcp_no_lease(self): """DHCP client failed to get required information (backoff timer increased to maximum value without success) - If DHCPv6 failed -> try DHCPv4 - If DHCPv4 failed -> reboot :return: """ dhcpv6_proc = self.processes['dhcpv6'] dhcpv4_proc = self.processes['dhcpv4'] if dhcpv6_proc.process is not None: # Kill DHCPv6 process if it still running, cleanup all related # stuff, but keep ipc_sock - it will be reused for dhcpv4 dhcpv6_proc.cleanup(close_ipc_sock=False) dhcpv6_proc.process = None if not self.fsm.can('dhcpv6_failed'): raise ValueError("Wrong state '%s' for dhcpv6_failed event", self.fsm.current) self.fsm.dhcpv6_failed() # Prepare "runtime" args args = [] for code, attr in self.dhcp_args_mapping.iteritems(): if isinstance(attr, basestring): attr_val = attr else: attr_val = self.db_adapter.get_leaf(attr) if attr_val is None or not isinstance(attr_val, basestring): self.logger.warning( "Attribute: %s not set in DB, ignoring ", attr) continue # append args in format: -x 0x0A:value args.extend(['-c', '{}:{}'.format(code, attr_val)]) self.logger.info("Starting DHCPv4 client ...") dhcpv4_proc.start(self.dhcp_msg_cb, args, dhcpv6_proc.ipc_sock) elif dhcpv4_proc.process is not None: dhcpv4_proc.cleanup() dhcpv4_proc.process = None self.logger.error("Both DHCPv6 & DHCPv4 failed - exiting ...") self.fsm.fatal_failure() else: raise ValueError("Received unexpected DHCP failed message") def _cleanup(self): """Cleanup method, this should be used only in scenario, when Manager is killed directly. :return: """ for process in self.processes.values(): process.cleanup() if self.sfd_stream is not None: self.sfd_stream.close() self.configure_remote_logging(None) exit(0) def testing_cleanup(self): """Cleanup method for testing purposes.""" for process in self.processes.values(): process.cleanup() if self.sfd_stream is not None: self.sfd_stream.close() # let the cleanup finish time.sleep(2) def fd_event_handler(self, signal_fd, eventmask): """Callback called by dispatcher when any signal is received on signalfd Reads signal info from fd and calls applicable signal handler. :return: """ del eventmask siginfo = signalfd_siginfo() if self.sfd_stream is None: try: self.sfd_stream = os.fdopen(signal_fd, 'rb', 0) except IOError, ex: self.logger.error("Failed to open signal fd - %s", os.strerror(ex.errno)) self._cleanup() return self.sfd_stream.readinto(siginfo) self._signal_handler(siginfo.ssi_signo, None)
class StatesTestCase(unittest.TestCase): def setUp(self): self.fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ] }) def test_invalid_state(self): self.assertRaises(AttributeError, getattr, self.fsm, 'wtfbbq') def test_state_conditions(self): """ This will check for example, if state is in clear, will be able to emit warn and will not be able to emit panic, calm or clear again. """ self.assertTrue(self.fsm.isstate('green')) self.assertTrue(self.fsm.can('warn')) self.assertFalse(self.fsm.can('panic')) self.assertTrue(self.fsm.cannot('panic')) self.assertFalse(self.fsm.can('clam')) self.assertFalse(self.fsm.can('clear')) def test_state_changing(self): self.assertTrue(self.fsm.isstate('green')) self.fsm.warn() self.assertTrue(self.fsm.isstate('yellow')) def test_state_cannot_be_called_twice(self): self.fsm.warn() self.assertTrue(self.fsm.isstate('yellow')) self.assertRaises(FysomError, self.fsm.warn) def test_auto_next_state(self): self.assertEqual('green', self.fsm.current) self.assertEqual('yellow', self.fsm.next()) self.assertEqual('yellow', self.fsm.current) def test_multiple_possibilities_for_next(self): fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'panic', 'src': 'green', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ] }) self.assertRaises(MultiplePossibilitesFound, fsm.next) def test_no_possibility_for_next(self): fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ] }) self.assertRaises(NoPossibilityFound, fsm.next)