From 1521e0a34b003af5577612a6eae6ede2fa2d0804 Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Tue, 10 Dec 2019 11:36:40 +0100 Subject: [PATCH] write configured parameters to the hardware writable parameters with a configured value should call write_ on initialization. + introduced 'initwrite' parameter property for more fine control over this + minor improvements in metaclass.py, param.py, commandhandler.py + rearranged test_modules.py Change-Id: I2eec45da40947a73d9c180f0f146eb62efbda2b3 Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/21986 Tested-by: JenkinsCodeReview Reviewed-by: Markus Zolliker --- secop/commandhandler.py | 113 +++++++++++++++-------- secop/metaclass.py | 1 - secop/modules.py | 70 +++++++++++--- secop/params.py | 15 ++- secop/poller.py | 4 +- test/test_commandhandler.py | 13 ++- test/test_modules.py | 177 ++++++++++++++++++++++++------------ test/test_params.py | 3 +- test/test_poller.py | 4 +- 9 files changed, 285 insertions(+), 115 deletions(-) diff --git a/secop/commandhandler.py b/secop/commandhandler.py index 612cb82..54cc3b7 100755 --- a/secop/commandhandler.py +++ b/secop/commandhandler.py @@ -20,12 +20,36 @@ # ***************************************************************************** """command handler -utility class for cases, where multiple parameters are treated with a common command. +Utility class for cases, where multiple parameters are treated with a common command. The support for LakeShore and similar protocols is already included. + +For read, instead of the methods read_ we write one method analyze_ +for all parameters with the same handler. Before analyze_ is called, the +reply is parsed and converted to values, which are then given as arguments. + +def analyze_(self, value1, value2, ...): + # we have to calculate parameters from the values (value1, value2 ...) + # and assign them to self. + # no return value is expected + +For write, instead of the methods write_" we write one method change_ +for all parameters with the same handler. + +def change_(self, new, value1, value2, ...): + # is a wrapper object around the module, containing already the new values. + # if READ_BEFORE_WRITE is True (the default), the additional arguments (value1, ...) + # must be in the argument list. They contain the values read from the hardware. + # If they are not needed, set READ_BEFORE_WRITE to False, or declare them as '*args'. + # The expression ('' in new) returns a boolean indicating, whether + # this parameter is subject to change. + # The return value must be either a sequence of values to be written to the hardware, + # which will be formatted by the handler, or None. The latter is used only in some + # special cases, when nothing has to be written. """ import re from secop.metaclass import Done +from secop.errors import ProgrammingError @@ -113,34 +137,38 @@ class CmdParser: class ChangeWrapper: - """store parameter changes before they are applied""" + """Wrapper around a module - def __init__(self, module, pname, value): + A ChangeWrapper instance is used as the 'new' argument for the change_ message. + new. is either the new, changed value or the old value from the module. + In addition '' indicates, whether is to be changed. + setting new. does not yet set the value on the module. + """ + def __init__(self, module, valuedict): self._module = module - setattr(self, pname, value) + for pname, value in valuedict.items(): + setattr(self, pname, value) def __getattr__(self, key): - """get values from module for unknown keys""" + """get current values from _module for unchanged parameters""" return getattr(self._module, key) - def apply(self, module): - """set only changed values""" - for k, v in self.__dict__.items(): - if k != '_module' and v != getattr(module, k): - setattr(module, k, v) - - def __repr__(self): - return ', '.join('%s=%r' % (k, v) for k, v in self.__dict__.items() if k != '_module') - + def __contains__(self, pname): + """check whether a specific parameter is to be changed""" + return pname in self.__dict__ class CmdHandlerBase: """generic command handler""" + READ_BEFORE_WRITE = True + # if READ_BEFORE_WRITE is True, a read is performed before a write, and the parsed + # additional parameters are added to the argument list of change_. def __init__(self, group): # group is used for calling the proper analyze_ and change_ methods self.group = group - self.parameters = {} + self.parameters = set() + self._module_class = None def parse_reply(self, reply): """return values from a raw reply""" @@ -179,11 +207,11 @@ class CmdHandlerBase: and registers the parameter in this handler """ - if not modclass in self.parameters: - self.parameters[modclass] = [] - # Make sure that parameters from different module classes are not mixed - # (not sure if this might happen) - self.parameters[modclass].append(pname) + self._module_class = self._module_class or modclass + if self._module_class != modclass: + raise ProgrammingError("the handler '%s' for '%s.%s' is already used in module '%s'" + % (self.group, modclass.__name__, pname, self._module_class.__name__)) + self.parameters.add(pname) return self.read def read(self, module): @@ -196,14 +224,15 @@ class CmdHandlerBase: reply = self.send_command(module) # convert them to parameters getattr(module, 'analyze_' + self.group)(*reply) - for pname in self.parameters[module.__class__]: + assert module.__class__ == self._module_class + for pname in self.parameters: if module.parameters[pname].readerror: # clear errors on parameters, which were not updated. # this will also inform all activated clients setattr(module, pname, getattr(module, pname)) except Exception as e: # set all parameters of this handler to error - for pname in self.parameters[module.__class__]: + for pname in self.parameters: module.setError(pname, e) raise return Done # parameters should be updated already @@ -215,23 +244,35 @@ class CmdHandlerBase: """ def wfunc(module, value, cmd=self, pname=pname): - # do a read of the current hw values - values = cmd.send_command(module) - # convert them to parameters - analyze = getattr(module, 'analyze_' + cmd.group) - analyze(*values) - # create wrapper object 'new' with changed parameter 'pname' - new = ChangeWrapper(module, pname, value) - # call change_* for calculation new hw values - values = getattr(module, 'change_' + cmd.group)(new, *values) - # send the change command and a query command - analyze(*cmd.send_change(module, *values)) - # update only changed values - new.apply(module) - return Done # parameter 'pname' should be changed already + cmd.write(module, {pname: value}) + return Done return wfunc + def write(self, module, valuedict, force_read=False): + """write values to the module + + When called from write_, valuedict contains only one item: + the parameter to be changed. + When called from initialization, valuedict may have more items. + """ + analyze = getattr(module, 'analyze_' + self.group) + if self.READ_BEFORE_WRITE or force_read: + # do a read of the current hw values + values = self.send_command(module) + # convert them to parameters + analyze(*values) + if not self.READ_BEFORE_WRITE: + values = () + # create wrapper object 'new' with changed parameter 'pname' + new = ChangeWrapper(module, valuedict) + # call change_* for calculation new hw values + values = getattr(module, 'change_' + self.group)(new, *values) + if values is None: # this indicates that nothing has to be written + return + # send the change command and a query command + analyze(*self.send_change(module, *values)) + class CmdHandler(CmdHandlerBase): """more evolved command handler diff --git a/secop/metaclass.py b/secop/metaclass.py index b89e1d6..de59834 100644 --- a/secop/metaclass.py +++ b/secop/metaclass.py @@ -119,7 +119,6 @@ class ModuleMeta(PropertyMeta): "and read_%s" % (pname, pname)) rfunc = handler else: - rfunc = attrs.get('read_' + pname, None) for base in bases: if rfunc is not None: break diff --git a/secop/modules.py b/secop/modules.py index 577809e..5e79f41 100644 --- a/secop/modules.py +++ b/secop/modules.py @@ -28,7 +28,7 @@ from collections import OrderedDict from secop.datatypes import EnumType, FloatRange, BoolType, IntRange, \ StringType, TupleOf, get_datatype, ArrayOf, TextType -from secop.errors import ConfigError, ProgrammingError, SECoPError +from secop.errors import ConfigError, ProgrammingError, SECoPError, BadValueError from secop.lib import formatException, formatExtendedStack, mkthread from secop.lib.enum import Enum from secop.metaclass import ModuleMeta @@ -178,9 +178,18 @@ class Module(HasProperties, metaclass=ModuleMeta): (self.name, k, ', '.join(self.parameters.keys()))) # 4) complain if a Parameter entry has no default value and - # is not specified in cfgdict + # is not specified in cfgdict and deal with parameters to be written. + self.writeDict = {} # values of parameters to be written for pname, pobj in self.parameters.items(): - if pname not in cfgdict: + if pname in cfgdict: + if not pobj.readonly and not pobj.initwrite is False: + # parameters given in cfgdict have to call write_ + try: + pobj.value = pobj.datatype(cfgdict[pname]) + except BadValueError as e: + raise ConfigError('%s.%s: %s' % (name, pname, e)) + self.writeDict[pname] = pobj.value + else: if pobj.default is None: if not pobj.poll: raise ConfigError('Module %s: Parameter %r has no default ' @@ -193,7 +202,18 @@ class Module(HasProperties, metaclass=ModuleMeta): # when not all hardware parameters are read because of startup timeout pobj.value = pobj.datatype(pobj.datatype.default) else: - cfgdict[pname] = pobj.default + try: + value = pobj.datatype(pobj.default) + except BadValueError as e: + raise ProgrammingError('bad default for %s.%s: %s' + % (name, pname, e)) + if pobj.initwrite: + # we will need to call write_ + # if this is not desired, the default must not be given + pobj.value = value + self.writeDict[pname] = value + else: + cfgdict[pname] = value # 5) 'apply' config: # pass values through the datatypes and store as attributes @@ -258,10 +278,36 @@ class Module(HasProperties, metaclass=ModuleMeta): self.log.debug('empty %s.startModule()' % self.__class__.__name__) started_callback() - def pollOne(self, pname): - """call read function and handle error logging""" + def pollOneParam(self, pname): + """poll parameter with proper error handling""" try: - getattr(self, 'read_' + pname)() + return getattr(self, 'read_'+ pname)() + except SECoPError as e: + self.log.error(str(e)) + except Exception as e: + self.log.error(formatException()) + + def writeOrPoll(self, pname): + """write configured value for a parameter, if any, else poll + + with proper error handling + """ + try: + pobj = self.parameters[pname] + if pobj.handler: + pnames = pobj.handler.parameters + valuedict = {n: self.writeDict.pop(n) for n in pnames if n in self.writeDict} + if valuedict: + self.log.info('write parameters %r', valuedict) + pobj.handler.write(self, valuedict, force_read=True) + return + pobj.handler.read(self) + else: + if pname in self.writeDict: + self.log.info('write parameter %s', pname) + getattr(self, 'write_'+ pname)(self.writeDict.pop(pname)) + else: + getattr(self, 'read_'+ pname)() except SECoPError as e: self.log.error(str(e)) except Exception as e: @@ -285,7 +331,7 @@ class Readable(Module): ) parameters = { 'value': Parameter('current value of the Module', readonly=True, - default=0., datatype=FloatRange(), + datatype=FloatRange(), poll=True, ), 'pollinterval': Parameter('sleeptime between polls', default=5, @@ -320,6 +366,8 @@ class Readable(Module): def __pollThread_inner(self, started_callback): """super simple and super stupid per-module polling thread""" + for pname in list(self.writeDict): + self.writeOrPoll(pname) i = 0 fastpoll = self.pollParams(i) started_callback() @@ -339,7 +387,7 @@ class Readable(Module): continue if nr % abs(int(pobj.poll)) == 0: # pollParams every 'pobj.pollParams' iteration - self.pollOne(pname) + self.pollOneParam(pname) return False @@ -350,7 +398,7 @@ class Writable(Readable): """ parameters = { 'target': Parameter('target value of the Module', - default=0., readonly=False, datatype=FloatRange(), + default=0, readonly=False, datatype=FloatRange(), ), } @@ -395,7 +443,7 @@ class Drivable(Writable): nr % abs(int(pobj.poll))) == 0: # poll always if pobj.poll is negative and fastpoll (i.e. Module is busy) # otherwise poll every 'pobj.poll' iteration - self.pollOne(pname) + self.pollOneParam(pname) return fastpoll def do_stop(self): diff --git a/secop/params.py b/secop/params.py index 29b2597..7c7df4c 100644 --- a/secop/params.py +++ b/secop/params.py @@ -26,7 +26,7 @@ from collections import OrderedDict from secop.datatypes import CommandType, DataType, StringType, BoolType, EnumType, DataTypeType, ValueType, OrType, \ NoneOr, TextType, IntRange -from secop.errors import ProgrammingError +from secop.errors import ProgrammingError, BadValueError from secop.properties import HasProperties, Property @@ -114,6 +114,8 @@ class Parameter(Accessible): settable=False, default=False), 'handler': Property('[internal] overload the standard read and write functions', ValueType(), export=False, default=None, mandatory=False, settable=False), + 'initwrite': Property('[internal] write this parameter on initialization (default None: write if given in config)', + NoneOr(BoolType()), export=False, default=None, mandatory=False, settable=False), } def __init__(self, description, datatype, ctr=None, unit=None, **kwds): @@ -126,7 +128,7 @@ class Parameter(Accessible): # goodie: make an instance from a class (forgotten ()???) datatype = datatype() else: - raise ValueError( + raise ProgrammingError( 'datatype MUST be derived from class DataType!') kwds['description'] = description @@ -139,6 +141,9 @@ class Parameter(Accessible): if self.handler and not self.poll: self.properties['poll'] = True + if self.readonly and self.initwrite: + raise ProgrammingError('can not have both readonly and initwrite!') + if self.constant is not None: self.properties['readonly'] = True # The value of the `constant` property should be the @@ -233,6 +238,12 @@ class Override(CountedObj): constant = obj.datatype(self.kwds.pop('constant')) self.kwds['constant'] = obj.datatype.export_value(constant) self.kwds['readonly'] = True + if 'datatype' in self.kwds and 'default' not in self.kwds: + try: + self.kwds['datatype'](obj.default) + except BadValueError: + # clear default, if it does not match datatype + props['default'] = None props.update(self.kwds) if self.reorder: diff --git a/secop/poller.py b/secop/poller.py index 3c02542..4102b76 100644 --- a/secop/poller.py +++ b/secop/poller.py @@ -186,7 +186,7 @@ class Poller(PollerBase): mininterval = interval due = max(lastdue + interval, pobj.timestamp + interval * 0.5) if now >= due: - module.pollOne(pname) + module.pollOneParam(pname) done = True lastdue = due due = max(lastdue + mininterval, now + min(self.maxwait, mininterval * 0.5)) @@ -214,7 +214,7 @@ class Poller(PollerBase): for _, queue in sorted(self.queues.items()): # do SLOW polls first for idx, (_, _, (_, module, pobj, pname, factor)) in enumerate(queue): lastdue = time.time() - module.pollOne(pname) + module.writeOrPoll(pname) due = lastdue + min(self.maxwait, module.pollinterval * factor) # in python 3 comparing tuples need some care, as not all objects # are comparable. Inserting a unique idx solves the problem. diff --git a/test/test_commandhandler.py b/test/test_commandhandler.py index 727a092..4206795 100644 --- a/test/test_commandhandler.py +++ b/test/test_commandhandler.py @@ -26,6 +26,7 @@ import pytest from secop.commandhandler import CmdParser, CmdHandler from secop.modules import Module, Parameter from secop.datatypes import FloatRange, StringType, IntRange, Property +from secop.errors import ProgrammingError @pytest.mark.parametrize('fmt, text, values, text2', [ ('%d,%d', '2,3', [2,3], None), @@ -99,7 +100,7 @@ def test_CmdHandler(): group2 = Hdl('group2', 'CMD?%(channel)d', '%g,%s,%d') - class TestModule(Module): + class Module1(Module): properties = { 'channel': Property('the channel', IntRange(), default=3), 'loop': Property('the loop', IntRange(), default=2), @@ -130,7 +131,8 @@ def test_CmdHandler(): data = Data() updates = {} - module = TestModule('mymodule', LoggerStub(), {'.description': ''}, ServerStub(updates)) + module = Module1('mymodule', LoggerStub(), {'.description': ''}, ServerStub(updates)) + print(updates) updates.clear() # get rid of updates from initialisation # for sendRecv @@ -182,3 +184,10 @@ def test_CmdHandler(): assert module.real == updates.pop('real') == 12.3 assert data.empty() assert not updates + + with pytest.raises(ProgrammingError): # can not use a handler for different modules + # pylint: disable=unused-variable + class Module2(Module): + parameters = { + 'simple': Parameter('a readonly', FloatRange(), default=0.77, handler=group1), + } diff --git a/test/test_modules.py b/test/test_modules.py index b1635e9..4dbbf86 100644 --- a/test/test_modules.py +++ b/test/test_modules.py @@ -17,6 +17,7 @@ # # Module authors: # Enrico Faulhaber +# Markus Zolliker # # ***************************************************************************** """test data types.""" @@ -25,93 +26,119 @@ #import pytest import threading -from secop.datatypes import BoolType, EnumType, FloatRange -from secop.metaclass import ModuleMeta +from secop.datatypes import BoolType, FloatRange, StringType from secop.modules import Communicator, Drivable, Module from secop.params import Command, Override, Parameter +from secop.poller import BasicPoller + + +class DispatcherStub: + def __init__(self, updates): + self.updates = updates + + def announce_update(self, moduleobj, pname, pobj): + self.updates.setdefault(moduleobj.name, {}) + self.updates[moduleobj.name][pname] = pobj.value + + def announce_update_error(self, moduleobj, pname, pobj, err): + self.updates['error', moduleobj.name, pname] = str(err) + + +class LoggerStub: + def debug(self, *args): + print(*args) + info = exception = debug + + +class ServerStub: + def __init__(self, updates): + self.dispatcher = DispatcherStub(updates) def test_Communicator(): - logger = type('LoggerStub', (object,), dict( - debug = lambda self, *a: print(*a), - info = lambda self, *a: print(*a), - ))() - - dispatcher = type('DispatcherStub', (object,), dict( - announce_update = lambda self, m, pn, pv: print('%s:%s=%r' % (m.name, pn, pv)), - ))() - - srv = type('ServerStub', (object,), dict( - dispatcher = dispatcher, - ))() - - o = Communicator('communicator',logger, {'.description':''}, srv) + o = Communicator('communicator', LoggerStub(), {'.description':''}, ServerStub({})) o.earlyInit() o.initModule() event = threading.Event() o.startModule(event.set) assert event.is_set() # event should be set immediately + def test_ModuleMeta(): - # pylint: disable=too-many-function-args - newclass1 = ModuleMeta.__new__(ModuleMeta, 'TestDrivable', (Drivable,), { - "parameters" : { + class Newclass1(Drivable): + parameters = { 'pollinterval': Override(reorder=True), 'param1' : Parameter('param1', datatype=BoolType(), default=False), 'param2': Parameter('param2', datatype=FloatRange(unit='Ohm'), default=True), "cmd": Command('stuff', argument=BoolType(), result=BoolType()) - }, - "commands": { + } + commands = { # intermixing parameters with commands is not recommended, # but acceptable for influencing the order 'a1': Parameter('a1', datatype=BoolType(), default=False), 'a2': Parameter('a2', datatype=BoolType(), default=True), - 'value': Override(datatype=BoolType(), default=True), + 'value': Override(datatype=StringType(), default='first'), 'cmd2': Command('another stuff', argument=BoolType(), result=BoolType()), - }, - "do_cmd": lambda self, arg: not arg, - "do_cmd2": lambda self, arg: not arg, - "read_param1": lambda self, *args: True, - "read_param2": lambda self, *args: False, - "read_a1": lambda self, *args: True, - "read_a2": lambda self, *args: False, - "read_value": lambda self, *args: True, - "init": lambda self, *args: [None for self.accessibles['value'].datatype in [EnumType('value', OK=1, Bad=2)]], - }) + } + pollerClass = BasicPoller + + def do_cmd(self, arg): + return not arg + + def do_cmd2(self, arg): + return not arg + + def read_param1(self): + return True + + def read_param2(self): + return False + + def read_a1(self): + return True + + def read_a2(self): + return True + + def read_value(self): + return 'second' + + # first inherited accessibles, then Overrides with reorder=True and new accessibles sortcheck1 = ['value', 'status', 'target', 'pollinterval', 'param1', 'param2', 'cmd', 'a1', 'a2', 'cmd2'] - # pylint: disable=too-many-function-args - newclass2 = ModuleMeta.__new__(ModuleMeta, 'UpperClass', (newclass1,), { - "parameters": { + class Newclass2(Newclass1): + parameters = { 'cmd2': Override('another stuff'), 'value': Override(datatype=FloatRange(unit='deg'), reorder=True), - 'a1': Override(datatype=FloatRange(unit='$/s'), reorder=True), - 'b2': Parameter('a2', datatype=BoolType(), default=True), - }, - }) + 'a1': Override(datatype=FloatRange(unit='$/s'), reorder=True, readonly=False), + 'b2': Parameter('', datatype=BoolType(), default=True, + poll=True, readonly=False, initwrite=True), + } + + def write_a1(self, value): + self._a1_written = value + return value + + def write_b2(self, value): + self._b2_written = value + return value + + def read_value(self): + return 0 + sortcheck2 = ['value', 'status', 'target', 'pollinterval', 'param1', 'param2', 'cmd', 'a2', 'cmd2', 'a1', 'b2'] - logger = type('LoggerStub', (object,), dict( - debug = lambda self, *a: print(*a), - info = lambda self, *a: print(*a), - exception = lambda self, *a: print(*a), - ))() - - dispatcher = type('DispatcherStub', (object,), dict( - announce_update = lambda self, m, pn, po: print('%s:%s=%r' % (m.name, pn, po.value)), - ))() - - srv = type('ServerStub', (object,), dict( - dispatcher = dispatcher, - ))() + logger = LoggerStub() + updates = {} + srv = ServerStub(updates) params_found = set() # set of instance accessibles objects = [] - for newclass, sortcheck in [(newclass1, sortcheck1), (newclass2, sortcheck2)]: + for newclass, sortcheck in [(Newclass1, sortcheck1), (Newclass2, sortcheck2)]: o1 = newclass('o1', logger, {'.description':''}, srv) o2 = newclass('o2', logger, {'.description':''}, srv) for obj in [o1, o2]: @@ -127,16 +154,48 @@ def test_ModuleMeta(): # HACK: atm. disabled to fix all other problems first. assert check_order + sorted(check_order) - o1 = newclass1('o1', logger, {'.description':''}, srv) - o2 = newclass2('o2', logger, {'.description':''}, srv) + # check for inital updates working properly + o1 = Newclass1('o1', logger, {'.description':''}, srv) + expectedBeforeStart = {'target': 0.0, 'status': [Drivable.Status.IDLE, ''], + 'param1': False, 'param2': 1.0, 'a1': 0.0, 'a2': True, 'pollinterval': 5.0, + 'value': 'first'} + assert updates.pop('o1') == expectedBeforeStart + o1.earlyInit() + event = threading.Event() + o1.startModule(event.set) + event.wait() + # should contain polled values + expectedAfterStart = {'status': [Drivable.Status.IDLE, ''], + 'value': 'second'} + assert updates.pop('o1') == expectedAfterStart + + # check in addition if parameters are written + o2 = Newclass2('o2', logger, {'.description':'', 'a1': 2.7}, srv) + # no update for b2, as this has to be written + expectedBeforeStart['a1'] = 2.7 + assert updates.pop('o2') == expectedBeforeStart + o2.earlyInit() + event = threading.Event() + o2.startModule(event.set) + event.wait() + # value has changed type, b2 and a1 are written + expectedAfterStart.update(value=0, b2=True, a1=2.7) + assert updates.pop('o2') == expectedAfterStart + assert o2._a1_written == 2.7 + assert o2._b2_written is True + + assert not updates + + o1 = Newclass1('o1', logger, {'.description':''}, srv) + o2 = Newclass2('o2', logger, {'.description':''}, srv) assert o2.parameters['a1'].datatype.unit == 'deg/s' - o2 = newclass2('o2', logger, {'.description':'', 'value.unit':'mm', 'param2.unit':'mm'}, srv) + o2 = Newclass2('o2', logger, {'.description':'', 'value.unit':'mm', 'param2.unit':'mm'}, srv) # check datatype is not shared assert o1.parameters['param2'].datatype.unit == 'Ohm' assert o2.parameters['param2'].datatype.unit == 'mm' # check '$' in unit works properly assert o2.parameters['a1'].datatype.unit == 'mm/s' - cfg = newclass2.configurables + cfg = Newclass2.configurables assert set(cfg.keys()) == {'export', 'group', 'description', 'meaning', 'visibility', 'implementation', 'interface_classes', 'target', 'stop', 'status', 'param1', 'param2', 'cmd', 'a2', 'pollinterval', 'b2', 'cmd2', 'value', @@ -147,8 +206,8 @@ def test_ModuleMeta(): 'description'} # check on the level of classes - # this checks newclass1 too, as it is inherited by newclass2 - for baseclass in newclass2.__mro__: + # this checks Newclass1 too, as it is inherited by Newclass2 + for baseclass in Newclass2.__mro__: # every cmd/param has to be collected to accessibles acs = getattr(baseclass, 'accessibles', None) if issubclass(baseclass, Module): diff --git a/test/test_params.py b/test/test_params.py index 555a2c8..ef4c6c8 100644 --- a/test/test_params.py +++ b/test/test_params.py @@ -27,6 +27,7 @@ import pytest from secop.datatypes import BoolType, IntRange from secop.params import Command, Override, Parameter, Parameters +from secop.errors import ProgrammingError def test_Command(): @@ -55,7 +56,7 @@ def test_Parameter(): p2 = Parameter('description2', datatype=IntRange(), constant=1) assert p1 != p2 assert p1.ctr != p2.ctr - with pytest.raises(ValueError): + with pytest.raises(ProgrammingError): Parameter(None, datatype=float) p3 = p1.copy() assert p1.ctr != p3.ctr diff --git a/test/test_poller.py b/test/test_poller.py index 1250cb1..7b985b3 100644 --- a/test/test_poller.py +++ b/test/test_poller.py @@ -157,9 +157,11 @@ class Module: def isBusy(self): return self.is_busy - def pollOne(self, pname): + def pollOneParam(self, pname): getattr(self, 'read_' + pname)() + writeOrPoll = pollOneParam + def __repr__(self): rdict = self.__dict__.copy() rdict.pop('parameters')