Skip to content
Snippets Groups Projects
Commit c101a6ce authored by Drew Devereux's avatar Drew Devereux
Browse files

Merge branch 'fatal_error_bug' into 'master'

Add base device state model tests, and fix fatal error bug

See merge request ska-telescope/lmc-base-classes!25
parents a1461091 e18a6eac
Branches
Tags 0.6.3
No related merge requests found
release=0.6.2 release=0.6.3
tag=lmcbaseclasses-0.6.2 tag=lmcbaseclasses-0.6.3
...@@ -25,6 +25,9 @@ The lmc-base-classe repository contains set of eight classes as mentioned in SKA ...@@ -25,6 +25,9 @@ The lmc-base-classe repository contains set of eight classes as mentioned in SKA
## Version History ## Version History
#### 0.6.3
- Fix omission of fatal_error transition from base device state machine.
#### 0.6.2 #### 0.6.2
- Fix issue with incorrect updates to transitions dict from inherited devices. - Fix issue with incorrect updates to transitions dict from inherited devices.
Only noticeable if running multiple devices of different types in the same Only noticeable if running multiple devices of different types in the same
......
...@@ -6,6 +6,14 @@ ...@@ -6,6 +6,14 @@
SKA BaseDevice SKA BaseDevice
============================================ ============================================
The SKABaseDevice implements the basic device state machine, as illustrated
below, but without, at present, a Standby state.
.. image:: images/device_state_diagram.png
:width: 400
:alt: Diagram of the device state machine showing states and transitions
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
......
docs/source/images/device_state_diagram.png

564 KiB

...@@ -477,7 +477,7 @@ class SKABaseDeviceStateModel(DeviceStateModel): ...@@ -477,7 +477,7 @@ class SKABaseDeviceStateModel(DeviceStateModel):
lambda self: self._set_dev_state(DevState.ON) lambda self: self._set_dev_state(DevState.ON)
), ),
('OFF', 'on_failed'): ( ('OFF', 'on_failed'): (
"FAULT", "FAULT (ENABLED)",
lambda self: self._set_dev_state(DevState.FAULT) lambda self: self._set_dev_state(DevState.FAULT)
), ),
('ON', 'off_succeeded'): ( ('ON', 'off_succeeded'): (
...@@ -485,7 +485,11 @@ class SKABaseDeviceStateModel(DeviceStateModel): ...@@ -485,7 +485,11 @@ class SKABaseDeviceStateModel(DeviceStateModel):
lambda self: self._set_dev_state(DevState.OFF) lambda self: self._set_dev_state(DevState.OFF)
), ),
('ON', 'off_failed'): ( ('ON', 'off_failed'): (
"FAULT", "FAULT (ENABLED)",
lambda self: self._set_dev_state(DevState.FAULT)
),
('ON', 'fatal_error'): (
"FAULT (ENABLED)",
lambda self: self._set_dev_state(DevState.FAULT) lambda self: self._set_dev_state(DevState.FAULT)
), ),
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
"""Release information for lmc-base-classes Python Package""" """Release information for lmc-base-classes Python Package"""
name = """lmcbaseclasses""" name = """lmcbaseclasses"""
version = "0.6.2" version = "0.6.3"
version_info = version.split(".") version_info = version.split(".")
description = """A set of generic base devices for SKA Telescope.""" description = """A set of generic base devices for SKA Telescope."""
author = "SKA India and SARAO and CSIRO" author = "SKA India and SARAO and CSIRO"
......
...@@ -8,8 +8,6 @@ from queue import Empty, Queue ...@@ -8,8 +8,6 @@ from queue import Empty, Queue
from tango import EventType from tango import EventType
from tango.test_context import DeviceTestContext from tango.test_context import DeviceTestContext
from ska.base import SKASubarrayStateModel
@pytest.fixture(scope="class") @pytest.fixture(scope="class")
def tango_context(request): def tango_context(request):
...@@ -67,14 +65,6 @@ def initialize_device(tango_context): ...@@ -67,14 +65,6 @@ def initialize_device(tango_context):
yield tango_context.device.Init() yield tango_context.device.Init()
@pytest.fixture(scope="function")
def state_model():
"""
Yields an SKASubarrayStateModel.
"""
yield SKASubarrayStateModel()
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def tango_change_event_helper(tango_context): def tango_change_event_helper(tango_context):
""" """
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
######################################################################################### #########################################################################################
"""Contain the tests for the SKABASE.""" """Contain the tests for the SKABASE."""
import itertools
import re import re
import pytest import pytest
...@@ -18,6 +19,7 @@ import tango ...@@ -18,6 +19,7 @@ import tango
from unittest import mock from unittest import mock
from tango import DevFailed, DevState from tango import DevFailed, DevState
from ska.base import SKABaseDeviceStateModel
from ska.base.control_model import ( from ska.base.control_model import (
AdminMode, ControlMode, HealthState, LoggingLevel, SimulationMode, TestMode AdminMode, ControlMode, HealthState, LoggingLevel, SimulationMode, TestMode
) )
...@@ -28,6 +30,8 @@ from ska.base.base_device import ( ...@@ -28,6 +30,8 @@ from ska.base.base_device import (
LoggingTargetError, LoggingTargetError,
TangoLoggingServiceHandler, TangoLoggingServiceHandler,
) )
from ska.base.faults import StateModelError
# PROTECTED REGION END # // SKABaseDevice.test_additional_imports # PROTECTED REGION END # // SKABaseDevice.test_additional_imports
# Device test case # Device test case
# PROTECTED REGION ID(SKABaseDevice.test_SKABaseDevice_decorators) ENABLED START # # PROTECTED REGION ID(SKABaseDevice.test_SKABaseDevice_decorators) ENABLED START #
...@@ -583,3 +587,145 @@ class TestSKABaseDevice(object): ...@@ -583,3 +587,145 @@ class TestSKABaseDevice(object):
# PROTECTED REGION ID(SKABaseDevice.test_testMode) ENABLED START # # PROTECTED REGION ID(SKABaseDevice.test_testMode) ENABLED START #
assert tango_context.device.testMode == TestMode.NONE assert tango_context.device.testMode == TestMode.NONE
# PROTECTED REGION END # // SKABaseDevice.test_testMode # PROTECTED REGION END # // SKABaseDevice.test_testMode
@pytest.fixture
def state_model():
yield SKABaseDeviceStateModel()
class TestSKABaseDeviceStateModel():
"""
Test cases for SKABaseDeviceStateModel.
"""
@pytest.mark.parametrize(
'state_under_test, action_under_test',
itertools.product(
["UNINITIALISED", "INIT_ENABLED", "INIT_DISABLED", "FAULT_ENABLED",
"FAULT_DISABLED", "DISABLED", "OFF", "ON"],
["init_started", "init_succeeded", "init_failed", "fatal_error",
"reset_succeeded", "reset_failed", "to_notfitted",
"to_offline", "to_online", "to_maintenance", "on_succeeded",
"on_failed", "off_succeeded", "off_failed"]
)
)
def test_state_machine(
self, state_model, state_under_test, action_under_test
):
"""
Test the subarray state machine: for a given initial state and
an action, does execution of that action, from that initial
state, yield the expected results? If the action was not allowed
from that initial state, does the device raise a DevFailed
exception? If the action was allowed, does it result in the
correct state transition?
:todo: support starting in different memorised adminModes
"""
states = {
"UNINITIALISED":
(None, None),
"FAULT_ENABLED":
([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.FAULT),
"FAULT_DISABLED":
([AdminMode.NOT_FITTED, AdminMode.OFFLINE], DevState.FAULT),
"INIT_ENABLED":
([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.INIT),
"INIT_DISABLED":
([AdminMode.NOT_FITTED, AdminMode.OFFLINE], DevState.INIT),
"DISABLED":
([AdminMode.NOT_FITTED, AdminMode.OFFLINE], DevState.DISABLE),
"OFF":
([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.OFF),
"ON":
([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON),
}
def assert_state(state):
(admin_modes, state) = states[state]
if admin_modes is not None:
assert state_model.admin_mode in admin_modes
if state is not None:
assert state_model.dev_state == state
transitions = {
('UNINITIALISED', 'init_started'): "INIT_ENABLED",
('INIT_ENABLED', 'to_notfitted'): "INIT_DISABLED",
('INIT_ENABLED', 'to_offline'): "INIT_DISABLED",
('INIT_ENABLED', 'to_online'): "INIT_ENABLED",
('INIT_ENABLED', 'to_maintenance'): "INIT_ENABLED",
('INIT_ENABLED', 'init_succeeded'): 'OFF',
('INIT_ENABLED', 'init_failed'): 'FAULT_ENABLED',
('INIT_ENABLED', 'fatal_error'): "FAULT_ENABLED",
('INIT_DISABLED', 'to_notfitted'): "INIT_DISABLED",
('INIT_DISABLED', 'to_offline'): "INIT_DISABLED",
('INIT_DISABLED', 'to_online'): "INIT_ENABLED",
('INIT_DISABLED', 'to_maintenance'): "INIT_ENABLED",
('INIT_DISABLED', 'init_succeeded'): 'DISABLED',
('INIT_DISABLED', 'init_failed'): 'FAULT_DISABLED',
('INIT_DISABLED', 'fatal_error'): "FAULT_DISABLED",
('FAULT_DISABLED', 'to_notfitted'): "FAULT_DISABLED",
('FAULT_DISABLED', 'to_offline'): "FAULT_DISABLED",
('FAULT_DISABLED', 'to_online'): "FAULT_ENABLED",
('FAULT_DISABLED', 'to_maintenance'): "FAULT_ENABLED",
('FAULT_DISABLED', 'reset_succeeded'): "DISABLED",
('FAULT_DISABLED', 'reset_failed'): "FAULT_DISABLED",
('FAULT_DISABLED', 'fatal_error'): "FAULT_DISABLED",
('FAULT_ENABLED', 'to_notfitted'): "FAULT_DISABLED",
('FAULT_ENABLED', 'to_offline'): "FAULT_DISABLED",
('FAULT_ENABLED', 'to_online'): "FAULT_ENABLED",
('FAULT_ENABLED', 'to_maintenance'): "FAULT_ENABLED",
('FAULT_ENABLED', 'reset_succeeded'): "OFF",
('FAULT_ENABLED', 'reset_failed'): "FAULT_ENABLED",
('FAULT_ENABLED', 'fatal_error'): "FAULT_ENABLED",
('DISABLED', 'to_notfitted'): "DISABLED",
('DISABLED', 'to_offline'): "DISABLED",
('DISABLED', 'to_online'): "OFF",
('DISABLED', 'to_maintenance'): "OFF",
('DISABLED', 'fatal_error'): "FAULT_DISABLED",
('OFF', 'to_notfitted'): "DISABLED",
('OFF', 'to_offline'): "DISABLED",
('OFF', 'to_online'): "OFF",
('OFF', 'to_maintenance'): "OFF",
('OFF', 'on_succeeded'): "ON",
('OFF', 'on_failed'): "FAULT_ENABLED",
('OFF', 'fatal_error'): "FAULT_ENABLED",
('ON', 'off_succeeded'): "OFF",
('ON', 'off_failed'): "FAULT_ENABLED",
('ON', 'fatal_error'): "FAULT_ENABLED",
}
setups = {
"UNINITIALISED": [],
"INIT_ENABLED": ['init_started'],
"INIT_DISABLED": ['init_started', 'to_offline'],
"FAULT_ENABLED": ['init_started', 'init_failed'],
"FAULT_DISABLED": ['init_started', 'to_offline', 'init_failed'],
"OFF": ['init_started', 'init_succeeded'],
"DISABLED": ['init_started', 'init_succeeded', 'to_offline'],
"ON": ['init_started', 'init_succeeded', 'on_succeeded'],
}
# state = "UNINITIALISED" # for test debugging only
# assert_state(state) # for test debugging only
# Put the device into the state under test
for action in setups[state_under_test]:
state_model.perform_action(action)
# state = transitions[state, action] # for test debugging only
# assert_state(state) # for test debugging only
# Check that we are in the state under test
assert_state(state_under_test)
# Test that the action under test does what we expect it to
if (state_under_test, action_under_test) in transitions:
# Action should succeed
state_model.perform_action(action_under_test)
assert_state(transitions[(state_under_test, action_under_test)])
else:
# Action should fail and the state should not change
with pytest.raises(StateModelError):
state_model.perform_action(action_under_test)
assert_state(state_under_test)
...@@ -15,7 +15,7 @@ import pytest ...@@ -15,7 +15,7 @@ import pytest
from tango import DevState, DevFailed from tango import DevState, DevFailed
# PROTECTED REGION ID(SKASubarray.test_additional_imports) ENABLED START # # PROTECTED REGION ID(SKASubarray.test_additional_imports) ENABLED START #
from ska.base import SKASubarray, SKASubarrayResourceManager from ska.base import SKASubarray, SKASubarrayResourceManager, SKASubarrayStateModel
from ska.base.commands import ResultCode from ska.base.commands import ResultCode
from ska.base.control_model import ( from ska.base.control_model import (
AdminMode, ControlMode, HealthState, ObsMode, ObsState, SimulationMode, TestMode AdminMode, ControlMode, HealthState, ObsMode, ObsState, SimulationMode, TestMode
...@@ -579,6 +579,11 @@ def resource_manager(): ...@@ -579,6 +579,11 @@ def resource_manager():
yield SKASubarrayResourceManager() yield SKASubarrayResourceManager()
@pytest.fixture
def state_model():
yield SKASubarrayStateModel()
class TestSKASubarrayResourceManager: class TestSKASubarrayResourceManager:
def test_ResourceManager_assign(self, resource_manager): def test_ResourceManager_assign(self, resource_manager):
# create a resource manager and check that it is empty # create a resource manager and check that it is empty
......
...@@ -13,11 +13,17 @@ import pytest ...@@ -13,11 +13,17 @@ import pytest
from tango import DevState from tango import DevState
from ska.base import SKASubarrayStateModel
from ska.base.control_model import AdminMode, ObsState from ska.base.control_model import AdminMode, ObsState
from ska.base.faults import StateModelError from ska.base.faults import StateModelError
class TestSKASubarrayStateModel(): @pytest.fixture
def state_model():
yield SKASubarrayStateModel()
class TestSKASubarrayStateModel:
""" """
Test cases for SKASubarrayStateModel. Test cases for SKASubarrayStateModel.
""" """
...@@ -43,8 +49,9 @@ class TestSKASubarrayStateModel(): ...@@ -43,8 +49,9 @@ class TestSKASubarrayStateModel():
"restart_failed"] "restart_failed"]
) )
) )
def test_state_machine(self, state_model, def test_state_machine(
state_under_test, action_under_test): self, state_model, state_under_test, action_under_test
):
""" """
Test the subarray state machine: for a given initial state and Test the subarray state machine: for a given initial state and
an action, does execution of that action, from that initial an action, does execution of that action, from that initial
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment