Logging improvements to the test framework
This commit is contained in:
@@ -89,9 +89,6 @@ pytest tests/turboPmac1/ax1/test_common.py
|
|||||||
|
|
||||||
### Configuring pytest
|
### Configuring pytest
|
||||||
|
|
||||||
|
|
||||||
TODO: Add doc for --stresstest and --log flags and mention -s flag
|
|
||||||
|
|
||||||
And to run a specific test "test_something" within this file:
|
And to run a specific test "test_something" within this file:
|
||||||
```bash
|
```bash
|
||||||
pytest tests/turboPmac1/ax1/test_common.py -k 'test_something'
|
pytest tests/turboPmac1/ax1/test_common.py -k 'test_something'
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ pvprefix: DRVTESTS
|
|||||||
versions:
|
versions:
|
||||||
turboPmac: "1.5"
|
turboPmac: "1.5"
|
||||||
masterMacs: "1.5"
|
masterMacs: "1.5"
|
||||||
el734: "mathis_s"
|
el734: "0.1"
|
||||||
controllers:
|
controllers:
|
||||||
turboPmac1:
|
turboPmac1:
|
||||||
ip: "172.28.101.24"
|
ip: "172.28.101.24"
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ class CaprotoWrapper:
|
|||||||
Dummy function. In the original NICOS file, it is used to map EPICS
|
Dummy function. In the original NICOS file, it is used to map EPICS
|
||||||
status to NICOS status.
|
status to NICOS status.
|
||||||
"""
|
"""
|
||||||
0, ""
|
return 0, ""
|
||||||
|
|
||||||
def close_subscription(self, subscription):
|
def close_subscription(self, subscription):
|
||||||
subscription.clear()
|
subscription.clear()
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class Motor:
|
|||||||
|
|
||||||
__slots__ = ("_param_to_pv", "controller", "axis", "ip", "port",
|
__slots__ = ("_param_to_pv", "controller", "axis", "ip", "port",
|
||||||
"busypoll", "idlepoll", "pv", "default_position", 'logger',
|
"busypoll", "idlepoll", "pv", "default_position", 'logger',
|
||||||
"_epics_wrapper")
|
"_epics_wrapper", "_wait_for_done_info_interval")
|
||||||
|
|
||||||
# Motor record fields
|
# Motor record fields
|
||||||
fields = {
|
fields = {
|
||||||
@@ -109,6 +109,7 @@ class Motor:
|
|||||||
'alarm_status': 'STAT',
|
'alarm_status': 'STAT',
|
||||||
'alarm_severity': 'SEVR',
|
'alarm_severity': 'SEVR',
|
||||||
'precision': 'MRES',
|
'precision': 'MRES',
|
||||||
|
'position_deadband': 'SPDB',
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, controller, axis, default_position=0):
|
def __init__(self, controller, axis, default_position=0):
|
||||||
@@ -123,6 +124,7 @@ class Motor:
|
|||||||
self.pv = f'{pvprefix}:{controller}:{axis}'
|
self.pv = f'{pvprefix}:{controller}:{axis}'
|
||||||
self.default_position = default_position
|
self.default_position = default_position
|
||||||
self._epics_wrapper = CaprotoWrapper(TIMEOUT)
|
self._epics_wrapper = CaprotoWrapper(TIMEOUT)
|
||||||
|
self._wait_for_done_info_interval = 5 # Default value, can be overwritten.
|
||||||
|
|
||||||
# This will store PV objects for each PV param and is populated by the init function
|
# This will store PV objects for each PV param and is populated by the init function
|
||||||
self._param_to_pv = {}
|
self._param_to_pv = {}
|
||||||
@@ -159,13 +161,19 @@ class Motor:
|
|||||||
self.logger.debug(f"read '{val}' from '{self._param_to_pv[pvparam]}'")
|
self.logger.debug(f"read '{val}' from '{self._param_to_pv[pvparam]}'")
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
def connection_change_callback(self, name, pvparam, is_connected, **kwargs):
|
||||||
|
if is_connected:
|
||||||
|
self.logger.debug('%s connected!', name)
|
||||||
|
else:
|
||||||
|
self.logger.warning('%s disconnected!', name)
|
||||||
|
|
||||||
def move(self, target):
|
def move(self, target):
|
||||||
|
|
||||||
# Set the new target
|
# Set the new target
|
||||||
self.put_pv('writepv', target)
|
self.put_pv('writepv', target)
|
||||||
|
|
||||||
if self.at_target(target):
|
if self.at_target(target):
|
||||||
self.logger.info(f"already at target '{target}'")
|
self.logger.info(f"received move to '{target}' command, but motor is already at target")
|
||||||
else:
|
else:
|
||||||
self.logger.info(f"moving to '{target}'")
|
self.logger.info(f"moving to '{target}'")
|
||||||
|
|
||||||
@@ -179,15 +187,13 @@ class Motor:
|
|||||||
# Set the new target
|
# Set the new target
|
||||||
self.put_pv('writepv', target)
|
self.put_pv('writepv', target)
|
||||||
|
|
||||||
# Is the motor already moving?
|
|
||||||
if self.get_pv('donemoving') != 0:
|
|
||||||
self.logger.info(f"starting move to '{target}'")
|
|
||||||
self.wait_for_start()
|
|
||||||
if self.at_target(target):
|
if self.at_target(target):
|
||||||
self.logger.info(f"already at target '{target}'")
|
self.logger.info(f"already at target '{target}'")
|
||||||
else:
|
else:
|
||||||
|
self.logger.info(f"starting move to '{target}'")
|
||||||
|
self.wait_for_start()
|
||||||
self.logger.info(f"moving to '{target}'")
|
self.logger.info(f"moving to '{target}'")
|
||||||
self.wait_for_done()
|
self.wait_for_done()
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
f"finished moving to '{target}', now at '{self.get_pv('readpv')}'")
|
f"finished moving to '{target}', now at '{self.get_pv('readpv')}'")
|
||||||
|
|
||||||
@@ -203,9 +209,12 @@ class Motor:
|
|||||||
if target is None:
|
if target is None:
|
||||||
target = self.get_pv('writepv')
|
target = self.get_pv('writepv')
|
||||||
|
|
||||||
|
position_deadband = self.get_pv('position_deadband')
|
||||||
|
precision = self.get_pv('precision')
|
||||||
|
|
||||||
return (self.get_pv('miss') == 0 and
|
return (self.get_pv('miss') == 0 and
|
||||||
math.isclose(self.get_pv('readpv'), target,
|
math.isclose(self.get_pv('readpv'), target,
|
||||||
rel_tol=1e-9, abs_tol=self.get_pv('precision')))
|
rel_tol=1e-9, abs_tol=max(position_deadband, precision)))
|
||||||
|
|
||||||
def wait_for_start(self, maxwaittime=None):
|
def wait_for_start(self, maxwaittime=None):
|
||||||
if maxwaittime is None:
|
if maxwaittime is None:
|
||||||
@@ -227,6 +236,7 @@ class Motor:
|
|||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
def wait_for_done(self):
|
def wait_for_done(self):
|
||||||
|
last_info = time.time()
|
||||||
while not self.get_pv('donemoving'):
|
while not self.get_pv('donemoving'):
|
||||||
if self.has_error():
|
if self.has_error():
|
||||||
error_msg = self.error_message()
|
error_msg = self.error_message()
|
||||||
@@ -238,6 +248,13 @@ class Motor:
|
|||||||
'Unknown error while waiting for completion of movement')
|
'Unknown error while waiting for completion of movement')
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# Logging functionality
|
||||||
|
now = time.time()
|
||||||
|
if now > last_info + self._wait_for_done_info_interval:
|
||||||
|
last_info = now
|
||||||
|
current_position = self.get_pv('readpv')
|
||||||
|
self.logger.info(f"still moving, current position is {current_position}")
|
||||||
|
|
||||||
def has_error(self):
|
def has_error(self):
|
||||||
return self.get_pv('alarm_severity') == 'MAJOR'
|
return self.get_pv('alarm_severity') == 'MAJOR'
|
||||||
|
|
||||||
@@ -250,15 +267,15 @@ class Motor:
|
|||||||
return bool(str[15])
|
return bool(str[15])
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.logger.info(f"stopping")
|
self.logger.info("stopping")
|
||||||
self.put_pv('stop', 1)
|
self.put_pv('stop', 1)
|
||||||
|
|
||||||
def homeforward(self):
|
def homeforward(self):
|
||||||
self.logger.info(f"homing forward")
|
self.logger.info("homing forward")
|
||||||
self.put_pv('homeforward', 1)
|
self.put_pv('homeforward', 1)
|
||||||
|
|
||||||
def homereverse(self):
|
def homereverse(self):
|
||||||
self.logger.info(f"homing reverse")
|
self.logger.info("homing reverse")
|
||||||
self.put_pv('homereverse', 1)
|
self.put_pv('homereverse', 1)
|
||||||
|
|
||||||
def set_highlimit(self, high_limit):
|
def set_highlimit(self, high_limit):
|
||||||
@@ -287,6 +304,20 @@ class SinqMotor(Motor):
|
|||||||
'errormsgpv': '-MsgTxt',
|
'errormsgpv': '-MsgTxt',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def __init__(self, controller, axis, default_position=0):
|
||||||
|
Motor.__init__(self, controller, axis, default_position)
|
||||||
|
|
||||||
|
# Log initial error messages
|
||||||
|
message = self.error_message()
|
||||||
|
if message:
|
||||||
|
self.logger.error(message)
|
||||||
|
|
||||||
|
# A callback which logs error messages
|
||||||
|
self._epics_wrapper.subscribe(self._get_pvs()['errormsgpv'], 'errormsgpv',
|
||||||
|
self.error_message_callback,
|
||||||
|
self.connection_change_callback)
|
||||||
|
|
||||||
|
|
||||||
def _get_pvs(self, pvs={}):
|
def _get_pvs(self, pvs={}):
|
||||||
"""
|
"""
|
||||||
:return: Dict of PV aliases and actual PV names.
|
:return: Dict of PV aliases and actual PV names.
|
||||||
@@ -301,6 +332,16 @@ class SinqMotor(Motor):
|
|||||||
def error_message(self):
|
def error_message(self):
|
||||||
return self.get_pv('errormsgpv', as_string=True)
|
return self.get_pv('errormsgpv', as_string=True)
|
||||||
|
|
||||||
|
def error_message_callback(self, name, param, value, units, severity,
|
||||||
|
message, **kwargs):
|
||||||
|
"""
|
||||||
|
Override this for custom behaviour in subclasses.
|
||||||
|
"""
|
||||||
|
message = self.error_message()
|
||||||
|
if message:
|
||||||
|
self.logger.error(message)
|
||||||
|
|
||||||
|
|
||||||
def enable_and_wait(self, timeout=20):
|
def enable_and_wait(self, timeout=20):
|
||||||
self.enable(1)
|
self.enable(1)
|
||||||
self.wait_enabled(timeout)
|
self.wait_enabled(timeout)
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ def test_move_to_high_limit_switch(motor: Motor):
|
|||||||
def test_move_while_move_same_direction(motor: Motor):
|
def test_move_while_move_same_direction(motor: Motor):
|
||||||
motor.stop()
|
motor.stop()
|
||||||
motor.wait_for_done()
|
motor.wait_for_done()
|
||||||
move_while_move(motor, -30, -10, 2)
|
move_while_move(motor, -30, -10, 5)
|
||||||
|
|
||||||
|
|
||||||
def test_move_while_move_opposite_direction(motor: Motor):
|
def test_move_while_move_opposite_direction(motor: Motor):
|
||||||
motor.stop()
|
motor.stop()
|
||||||
motor.wait_for_done()
|
motor.wait_for_done()
|
||||||
move_while_move(motor, 10, -10, 2)
|
move_while_move(motor, 10, -10, 5)
|
||||||
|
|
||||||
|
|
||||||
def test_stop(motor: Motor):
|
def test_stop(motor: Motor):
|
||||||
|
|||||||
Reference in New Issue
Block a user