Source code for test.test_symplehfsm

#!/usr/bin/python
# -*- coding: utf-8 -*-

u"""
TODO: modul doc string
"""

__author__ = u"dr0iddr0id {at} gmail [dot] com (C) 2010"

import operator

import symplehfsm
from symplehfsm import BaseState
from symplehfsm import Transition
from symplehfsm import BaseHFSMTests
from symplehfsm import Structure
from symplehfsm import SympleDictHFSM


import unittest



   



# -----------------------------------------------------------------------------
# TODO: loop action back to event -> queue?
# -----------------------------------------------------------------------------

# making a statemachine testable based on: http://accu.org/index.php/journals/1548


# -----------------------------------------------------------------------------
#
# Statechart used to test the SympleHFSM
# based on 
# [Samek] Miro Samek, Practical Statecharts in C/C++, CMP Books 2002. 
# There's a companion website with additional information: http://www.quantum-leaps.com
# taken from: http://accu.org/index.php/journals/252
#
# see also: http://en.wikipedia.org/wiki/UML_state_machine#Local_versus_external_transitions
# making a statemachine testable based on: http://accu.org/index.php/journals/1548






# -----------------------------------------------------------------------------
[docs]class BaseStateTests(unittest.TestCase):
[docs] def setUp(self): self.state = BaseState()
[docs] def test_name_is_set(self): name = "XXX" state = BaseState(name=name) self.assertTrue(name in str(state), "name not set")
[docs] def test_parent_set_through_constructor(self): self.state.name = "parend" child = BaseState("child", self.state) self.assertTrue(child.parent == self.state, "wrong parent set to child") self.assertTrue(child in self.state.children, "child not registered in parent")
[docs] def test_self_is_not_child(self): self.assertTrue(self.state.is_child(self.state) == False, "the state itself should not be a child")
[docs] def test_direct_child(self): child = BaseState() self.state.add(child) self.assertTrue(child.is_child(self.state), "child should be a child")
[docs] def test_multiple_children(self): child1 = BaseState() child2 = BaseState() child3 = BaseState() self.state.add(child1) child1.add(child2) child2.add(child3) self.assertTrue(child1.is_child(self.state), "child1 should be a child") self.assertTrue(child2.is_child(self.state), "child2 should be a child") self.assertTrue(child3.is_child(self.state), "child3 should be a child") self.assertTrue(child2.is_child(child1), "child2 should be a child") self.assertTrue(child3.is_child(child1), "child3 should be a child") self.assertTrue(child3.is_child(child2), "child3 should be a child")
[docs] def test_representation_is_string(self): repr = str(self.state) self.assertTrue(len(repr) > 0, "should not be empty") self.assertTrue(isinstance(repr, type("")), "should be a string")
[docs] def test_parent_is_not_child(self): child = BaseState() self.assertTrue(self.state.is_child(child) == False, "parent should not be a child")
[docs] def test_self_hast_not_self_as_child(self): self.assertTrue(self.state.has_child(self.state) == False, "the state itself should not be a child")
[docs] def test_has_direct_child(self): child = BaseState() self.state.add(child) self.assertTrue(self.state.has_child(child), "child should be a child")
[docs] def test_has_multiple_children(self): child1 = BaseState() child2 = BaseState() child3 = BaseState() self.state.add(child1) child1.add(child2) child2.add(child3) self.assertTrue(self.state.has_child(child1), "child1 should be a child") self.assertTrue(self.state.has_child(child2), "child2 should be a child") self.assertTrue(self.state.has_child(child3), "child3 should be a child") self.assertTrue(child1.has_child(child2), "child2 should be a child") self.assertTrue(child1.has_child(child3), "child3 should be a child") self.assertTrue(child2.has_child(child3), "child3 should be a child")
[docs] def test_child_has_not_parent_as_child(self): child = BaseState() self.assertTrue(child.has_child(self.state) == False, "parent should not be a child")
[docs] def test_add_child(self): child = BaseState() self.state.add(child) self.assertTrue(child.parent == self.state, "child has wrong parent") self.assertTrue(self.state.children[0] == child, "parent state should have child")
[docs] def test_add_child_and_initial(self): child = BaseState() self.state.add(child, True) self.assertTrue(child.parent == self.state, "child has wrong parent") self.assertTrue(self.state.children[0] == child, "parent state should have child") self.assertTrue(self.state.initial == child, "initial state not set")
[docs] def test_add_child_initial_only_once(self): child = BaseState() child2 = BaseState() self.state.add(child, True) try: self.state.add(child2, True) self.fail("should throw exception when trying to add another state as inital") except BaseState.InitialStateAlreadySetError: pass
[docs] def test_add_returns_child(self): child = BaseState() ret = self.state.add(child) self.assertTrue(ret==child, "add should return added child")
[docs] def test_check_if_child_has_parent(self): child = BaseState() child.parent = 1 try: self.state.add(child) self.fail("should have thrown exception because child has a parent set already") except BaseState.ParentAlreadySetError: pass
[docs] def test_remove_child(self): child = BaseState() self.state.add(child) self.state.remove(child) self.assertTrue(child.parent is None, "parent of removed child still set") try: self.state.children.index(child) self.fail("child should not be in children after remove") except ValueError: pass except Exception, e: self.fail(str(e))
[docs] def test_remove_child_set_as_initial(self): child = BaseState() child2 = BaseState() self.state.add(child, True) self.state.add(child2) self.state.remove(child, child2) self.assertTrue(child.parent is None, "parent of removed child still set") self.assertTrue(self.state.initial == child2, "initial not reset to other child") try: self.state.children.index(child) self.fail("child should not be in children after remove") except ValueError: pass
[docs] def test_remove_child_set_as_initial_exception(self): child = BaseState() self.state.add(child, True) try: self.state.remove(child) self.fail("removing initial state without replacement should raise a Exception") except BaseState.InitialNotReplacedError, e: pass
[docs] def test_remove_child_set_as_initial_replacment_is_child(self): child = BaseState() child2 = BaseState() self.state.add(child, True) try: self.state.remove(child, child2) self.fail("should raise exception because replacement child is not a child of the state") except BaseState.ReplacementStateIsNotChildError: pass
[docs] def test_check_initial_not_set(self): child = BaseState() child2 = BaseState() self.state.add(child) self.state.add(child2) try: self.state.check_consistency() self.fail("should throw initial missing exception") except BaseState.InitialNotSetError: pass
[docs] def test_check_initial(self): child = BaseState() child2 = BaseState() self.state.add(child) self.state.add(child2, True) try: self.state.check_consistency() except Exception, e: self.fail(str(e))
[docs] def test_check_initial_wrong_parent(self): child = BaseState() child2 = BaseState() self.state.add(child) self.state.add(child2, True) child.parent = child2 try: self.state.check_consistency() self.fail("should raise WrongParentError") except BaseState.WrongParentError, e: pass # -----------------------------------------------------------------------------
[docs]class SympleHFSMTransitionTests(unittest.TestCase):
[docs] def setUp(self): # def __init__(self, target_state, action=None, guard=None, name=None): self.target = "target" self.action = "action" self.guard = "guard" self.name = "name" self.transition = Transition(self.target, self.action, self.guard, self.name)
[docs] def test_attributes(self): self.assertTrue(self.transition.target == self.target) self.assertTrue(self.transition.action == self.action) self.assertTrue(self.transition.guard == self.guard) self.assertTrue(self.transition.name == self.name)
[docs] def test_representation_is_string(self): repr = str(self.transition) self.assertTrue(len(repr) > 0, "should not be empty") self.assertTrue(isinstance(repr, type("")), "should be a string") # -----------------------------------------------------------------------------
[docs]class StructureTests(unittest.TestCase):
[docs] def setUp(self): self.name = "asdlfkjdflkjUIPOIU" self.structure = Structure(self.name) self.state1 = "ARBD" self.state2 = 234234234 self.state3 = object()
[docs] def test_that_name_is_set(self): self.assertTrue(self.structure.name == self.name, "name not set!")
[docs] def test_add_root(self): # def add_state(self, state, parent, initial, entry_action=None, exit_action=None): self.structure.add_state(self.state1, None, None) self.assertTrue(self.structure.root == self.state1, "no or wrong root set") self.assertTrue(len(self.structure.states) == 1, "only one state (root) should be present")
[docs] def test_adding_second_root_raises_exception(self): self.structure.add_state(self.state1, None, None) try: self.structure.add_state(self.state2, None, None) self.fail("should have raised a RootAlreadySetOrParentMissingError exception") except Structure.RootAlreadySetOrParentMissingError, e: pass
[docs] def test_adding_state_twice_raises_exception(self): self.structure.add_state(self.state1, None, None) self.structure.add_state(self.state3, self.state1, None) try: self.structure.add_state(self.state3, self.state1, None) self.fail("should have raised StateIdentifierAlreadyUsed exception") except Structure.StateIdentifierAlreadyUsed, e: pass
[docs] def test_add_state(self): self.structure.add_state(self.state1, None, None) self.structure.add_state(self.state2, self.state1, None) parent = self.structure.states[self.state1] child = self.structure.states[self.state2] self.assertTrue(parent.has_child(child), "child not added") self.assertTrue(child.parent == parent, "wrong parent set to child")
[docs] def test_add_transition_to_unkown_state(self): try: # def add_trans(self, state, event, target, action=None, guard=None, name=None): self.structure.add_trans("aaaa", "event1", self.state2) self.fail("should have raised StateUnknownError") except symplehfsm.StateUnknownError, e: pass
[docs] def test_add_transition_unkown_target(self): self.structure.add_state(self.state1, None, None) try: self.structure.add_trans(self.state1, "event", "unkown_state_identifier") self.fail("should have raised StateUnknownError") except symplehfsm.StateUnknownError, e: pass
[docs] def test_add_transition_target_not_set(self): self.structure.add_state(self.state1, None, None) try: self.structure.add_trans(self.state1, "event", None) except symplehfsm.StateUnknownError, e: self.fail("should have raised StateUnknownError")
[docs] def test_representation_is_string(self): repr = str(self.structure) self.assertTrue(len(repr) > 0, "should not be empty") self.assertTrue(isinstance(repr, type("")), "should be a string")
[docs] def test_adding_transition_same_event_twice_on_same_state_raises_error(self): self.structure.add_state(self.state1, None, None) event_id = 12341234 self.structure.add_trans(self.state1, event_id, None) try: self.structure.add_trans(self.state1, event_id, None) self.fail("should have raised EventAlreadyDefinedError") except Structure.EventAlreadyDefinedError, e: pass
[docs] def test_adding_transitions(self): event_id = 12341234 event_id1 = 123412334 event_id2 = 1234 self.structure.add_state(self.state1, None, None) self.structure.add_state(self.state2, self.state1, None) self.structure.add_state(self.state3, self.state2, None) self.structure.add_trans(self.state1, event_id, None) self.structure.add_trans(self.state1, event_id1, None) self.structure.add_trans(self.state1, event_id2, None) self.structure.add_trans(self.state2, event_id, None) self.structure.add_trans(self.state3, event_id, None) # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # TODO: redo tests using a real states and a fully implemented statemachine (event-, action-interface, etc)
[docs]class SympleHFSMTests():#unittest.TestCase): """ This are the tests for the 'SympleHFSM' but not the normal usage! See demos for proper usage! """
[docs] class Actions(object):
[docs] class AEnum(object): ACTION1 = "ACTION1" ACTION2 = "ACTION2" # make it read only
AEnum.__setattr__ = None def __init__(self): self.captured = []
[docs] def action1(self): self.captured.append(self.AEnum.ACTION1)
[docs] def action2(self): self.captured.append(self.AEnum.ACTION2)
[docs] class Events(object):
[docs] def a(self): pass
[docs] def b(self): pass
[docs] class StateEvents(object):
[docs] def a(self, context): pass
[docs] def b(self, context): pass
[docs] class State1(StateEvents, BaseState):
[docs] def entry(self, context): context.captured.append(self)
[docs] def exit(self, context): context.captured.append(self)
[docs] def a(self, context): return None
[docs] def b(self, context): return None
[docs] def reentrant_event(self, context): context.sm.handle_event(context.methodcaller, context)
[docs] class State2(State1):
[docs] def a(self, context): return context.transition
[docs] def setUp(self): self.captured = [] self.state1 = self.State1() self.actions = self.Actions() self.sm = SympleHFSM(None, None)
[docs] def test_set_state(self): obj = self.State1() self.sm.set_state(obj) self.assertTrue(obj is self.sm.current_state, "state not set!") self.assertTrue(len(self.captured) == 0, "no entry-, exit- or transition-action should be executed: " + str(self.captured))
[docs] def test_raise_reentry_exception(self): self.sm.set_state(self.state1) self.methodcaller = operator.methodcaller('reentrant_event', self) try: self.sm.handle_event(self.methodcaller, self) self.fail("SympleHFSM.ReentrantEventException not raised!") except SympleHFSM.ReentrantEventException, e: pass except Exception, ex: self.fail("wrong exceptiong: " + str(ex))
[docs] def test_exit_call_order(self): # create state hirarchy root = self.State1(None, None) s1 = self.State1(None, root) s11 = self.State1(None, s1) self.sm.set_state(s11) # method to test self.sm.exit(self) # verify result expected = [s11, s1, root] self.match_order(expected, self.captured)
[docs] def test_init_call_order(self): # create state hirachy root = self.State1(None) s1 = self.State1(None, root) s2 = self.State1(None, s1) root.initial = s1 s1.initial = s2 # method to test self.sm.init(root, self) # verify result expected = [root, s1, s2] self.match_order(expected, self.captured)
[docs] def match_order(self, expected, captured): # verify result if len(expected) != len(captured): self.fail("number of calls did not match!" + str(expected) + str(captured)) for idx, s in enumerate(expected): if s is not captured[idx]: self.fail("s: " + str(s) + " did not match: " + str(captured[idx]))
[docs] def test_transition_guard_true(self): # define hirachy root = self.State1() s1 = self.State2() s11 = self.State1() s2 = self.State1() root.add(s1) s1.add(s11) root.add(s2) root.initial = s1 s1.initial = s11 self.transition = Transition(s2) # transition should have an action to test that as well self.sm.init(root, self) self.sm.handle_event( operator.methodcaller('a', self), self) self.assertTrue(self.sm.current_state == s2, "not in expected state s2") expected = [root, s1, s11, s11, s1, s2] # init + transition self.match_order(expected, self.captured)
[docs] def test_transition_guard_false(self): # define hirachy root = self.State1(None) s1 = self.State2(None, root) s11 = self.State1(None, s1) s2 = self.State1(None, root) root.initial = s1 s1.initial = s11 self.transition = Transition(s2, guard=lambda context: False) self.sm.init(root, self) self.sm.handle_event( operator.methodcaller('a', self), self) self.assertTrue(self.sm.current_state == s11, "not in expected state s11")
[docs] def test_transition_event_not_handled(self): # define hirachy root = self.State1(None) s1 = self.State2(None, root) # this state will handle the event using the transition s11 = self.State1(None, s1) s2 = self.State1(None, root) root.initial = s1 s1.initial = s11 self.transition = Transition(s2) self.sm.init(root, self) self.sm.handle_event( operator.methodcaller('a', self), self) self.assertTrue(self.sm.current_state == s2, "not in expected state s2")
[docs] def test_no_event_handling(self): # define hirachy root = self.State1(None) s1 = self.State2(root) s11 = self.State1(s1) s2 = self.State1(root) root.initial = s1 s1.initial = s11 self.sm.init(root, self) self.captured = [] self.sm.handle_event( operator.methodcaller('b', self), self) self.assertTrue(self.sm.current_state == s11, "not in expected state s11") self.assertTrue(len(self.captured) == 0, "should not changed any state")
[docs] def test_transition_normal(self): # define hirachy root = self.State1() s1 = self.State2(None, root) # this state will handle the event using the transition s11 = self.State1(None, s1) s2 = self.State1(None, root) root.initial = s1 s1.initial = s11 self.transition = Transition(s2, lambda context: context.captured.append("trans")) self.sm.init() self.captured = [] self.sm.handle_event( operator.methodcaller('a', self), self) self.assertTrue(self.sm.current_state == s2, "not in expected state s2") expected = [s11, s1, "trans", s2] self.match_order(expected, self.captured)
[docs] def test_transition_into_target_child_state(self): # define hirachy root = self.State1(None) s1 = self.State2(None, root) # this state will handle the event using the transition s11 = self.State1(None, s1) s2 = self.State1(None, root) s22 = self.State1(None, s2) s222 = self.State1(None, s22) root.initial = s1 s1.initial = s11 s2.initial = s22 s22.initial = s222 self.transition = Transition(s2) self.sm.init(root, self) self.captured = [] # clear init state changes self.sm.handle_event( operator.methodcaller('a', self), self) self.assertTrue(self.sm.current_state == s222, "not in expected state s222") expected = [s11, s1, s2, s22, s222] self.match_order(expected, self.captured) # def test_transition_sequence_normal(self): # # define hirachy # root = self.State1(None) # s1 = self.State2(root) # this state will handle the event using the transition # s11 = self.State1(s1) # s2 = self.State1(root) # s22 = self.State1(s2) # s222 = self.State1(s22) # root.initial = s1 # s1.initial = s11 # s2.initial = s22 # s22.initial = s222 # self.transition = Transition(s2, [s1], [s2]) # self.sm.init(root, self) # self.captured = [] # clear init state changes # #self.sm.handle_event( operator.methodcaller('a', self), self) # self.prove_transition_sequence("title", starting_state, event_funcs, expected_state, expected_actions, state_machine, resulting_actions): # self.assertTrue(self.sm.current_state == s222, "not in expected state s222") # expected = [s11, s1, s2, s22, s222] # self.match_order(expected, self.captured) # def test_transition_sequence_wrong_actions(self): # def test_transition_sequence_wrong_final_state(self): # -----------------------------------------------------------------------------
[docs]class BaseHFSMTestsTests(unittest.TestCase):
[docs] def setUp(self): # self.actions = DebugActions() # self.sm = StateMachine(self.actions) # starting = None # event_func = None # expected_state = None # expected_actions = None # self.test_vector = BaseFSMTests.TestVector("", starting, event_func, expected_state, expected_actions) pass
[docs] def test_prove_one_transition_simple(self): # raise NotImplementedError() pass # -----------------------------------------------------------------------------