#!/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 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
# -----------------------------------------------------------------------------