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
release=0.6.2
tag=lmcbaseclasses-0.6.2
release=0.6.3
tag=lmcbaseclasses-0.6.3
......@@ -25,6 +25,9 @@ The lmc-base-classe repository contains set of eight classes as mentioned in SKA
## Version History
#### 0.6.3
- Fix omission of fatal_error transition from base device state machine.
#### 0.6.2
- Fix issue with incorrect updates to transitions dict from inherited devices.
Only noticeable if running multiple devices of different types in the same
......
......@@ -6,6 +6,14 @@
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::
:maxdepth: 2
......
......@@ -477,7 +477,7 @@ class SKABaseDeviceStateModel(DeviceStateModel):
lambda self: self._set_dev_state(DevState.ON)
),
('OFF', 'on_failed'): (
"FAULT",
"FAULT (ENABLED)",
lambda self: self._set_dev_state(DevState.FAULT)
),
('ON', 'off_succeeded'): (
......@@ -485,7 +485,11 @@ class SKABaseDeviceStateModel(DeviceStateModel):
lambda self: self._set_dev_state(DevState.OFF)
),
('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)
),
......
......@@ -7,7 +7,7 @@
"""Release information for lmc-base-classes Python Package"""
name = """lmcbaseclasses"""
version = "0.6.2"
version = "0.6.3"
version_info = version.split(".")
description = """A set of generic base devices for SKA Telescope."""
author = "SKA India and SARAO and CSIRO"
......
......@@ -8,8 +8,6 @@ from queue import Empty, Queue
from tango import EventType
from tango.test_context import DeviceTestContext
from ska.base import SKASubarrayStateModel
@pytest.fixture(scope="class")
def tango_context(request):
......@@ -67,14 +65,6 @@ def initialize_device(tango_context):
yield tango_context.device.Init()
@pytest.fixture(scope="function")
def state_model():
"""
Yields an SKASubarrayStateModel.
"""
yield SKASubarrayStateModel()
@pytest.fixture(scope="function")
def tango_change_event_helper(tango_context):
"""
......
......@@ -8,6 +8,7 @@
#########################################################################################
"""Contain the tests for the SKABASE."""
import itertools
import re
import pytest
......@@ -18,6 +19,7 @@ import tango
from unittest import mock
from tango import DevFailed, DevState
from ska.base import SKABaseDeviceStateModel
from ska.base.control_model import (
AdminMode, ControlMode, HealthState, LoggingLevel, SimulationMode, TestMode
)
......@@ -28,6 +30,8 @@ from ska.base.base_device import (
LoggingTargetError,
TangoLoggingServiceHandler,
)
from ska.base.faults import StateModelError
# PROTECTED REGION END # // SKABaseDevice.test_additional_imports
# Device test case
# PROTECTED REGION ID(SKABaseDevice.test_SKABaseDevice_decorators) ENABLED START #
......@@ -583,3 +587,145 @@ class TestSKABaseDevice(object):
# PROTECTED REGION ID(SKABaseDevice.test_testMode) ENABLED START #
assert tango_context.device.testMode == TestMode.NONE
# 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
from tango import DevState, DevFailed
# 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.control_model import (
AdminMode, ControlMode, HealthState, ObsMode, ObsState, SimulationMode, TestMode
......@@ -579,6 +579,11 @@ def resource_manager():
yield SKASubarrayResourceManager()
@pytest.fixture
def state_model():
yield SKASubarrayStateModel()
class TestSKASubarrayResourceManager:
def test_ResourceManager_assign(self, resource_manager):
# create a resource manager and check that it is empty
......
......@@ -13,11 +13,17 @@ import pytest
from tango import DevState
from ska.base import SKASubarrayStateModel
from ska.base.control_model import AdminMode, ObsState
from ska.base.faults import StateModelError
class TestSKASubarrayStateModel():
@pytest.fixture
def state_model():
yield SKASubarrayStateModel()
class TestSKASubarrayStateModel:
"""
Test cases for SKASubarrayStateModel.
"""
......@@ -43,8 +49,9 @@ class TestSKASubarrayStateModel():
"restart_failed"]
)
)
def test_state_machine(self, state_model,
state_under_test, action_under_test):
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
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment