Skip to content

Commit 89dc86a

Browse files
Rewrite internal use of process flag
1 parent 6efe75d commit 89dc86a

File tree

2 files changed

+101
-7
lines changed

2 files changed

+101
-7
lines changed

softioc/device.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,6 @@ def __init__(self, name, **kargs):
178178

179179
self.__validate = kargs.pop('validate', None)
180180
self.__always_update = kargs.pop('always_update', False)
181-
self.__enable_write = True
182181

183182
if 'initial_value' in kargs:
184183
value = self._value_to_epics(kargs.pop('initial_value'))
@@ -239,8 +238,7 @@ def _process(self, record):
239238
return EPICS_OK
240239

241240
python_value = self._epics_to_value(value)
242-
if self.__enable_write and self.__validate and \
243-
not self.__validate(self, python_value):
241+
if self.__validate and not self.__validate(self, python_value):
244242
# Asynchronous validation rejects value, so restore the last good
245243
# value.
246244
self._write_value(record, self._value[0])
@@ -249,7 +247,7 @@ def _process(self, record):
249247
# Value is good. Hang onto it, let users know the value has changed
250248
self._value = (value, severity, alarm)
251249
record.UDF = 0
252-
if self.__on_update and self.__enable_write:
250+
if self.__on_update:
253251
record.PACT = self._blocking
254252
dispatcher(
255253
self.__on_update,
@@ -287,9 +285,15 @@ def set(self, value, process=True,
287285
else:
288286
# The array parameter is used to keep the raw pointer alive
289287
dbf_code, length, data, array = self._value_to_dbr(value)
290-
self.__enable_write = process
288+
289+
# If we do process we instead do this inside _process, allowing
290+
# validation to potentially refuse the update.
291+
# However if we do not process, we must do this here to keep the
292+
# Python and EPICS values in line
293+
if not process:
294+
self._value = (value, severity, alarm)
295+
291296
db_put_field_process(_record.NAME, dbf_code, data, length, process)
292-
self.__enable_write = True
293297

294298
def get(self):
295299
return self._epics_to_value(self._value[0])

tests/test_records.py

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,10 +381,12 @@ def out_records(self, request):
381381

382382
def validate_always_pass(self, record, new_val):
383383
"""Validation method that always allows changes"""
384+
log("VALIDATE: Returning True")
384385
return True
385386

386387
def validate_always_fail(self, record, new_val):
387388
"""Validation method that always rejects changes"""
389+
log("VALIDATE: Returning False")
388390
return False
389391

390392
def validate_ioc_test_func(
@@ -446,7 +448,6 @@ def validate_test_runner(
446448
# Wait for message that IOC has started
447449
select_and_recv(parent_conn, "R")
448450

449-
450451
# Suppress potential spurious warnings
451452
_channel_cache.purge()
452453

@@ -1553,3 +1554,92 @@ def test_set_alarm_severity_status(self, set_enum):
15531554
_channel_cache.purge()
15541555
parent_conn.send("D") # "Done"
15551556
process.join(timeout=TIMEOUT)
1557+
1558+
1559+
class TestProcess:
1560+
"""Tests related to processing - checking values are as expected
1561+
between the EPICS and Python layers. """
1562+
1563+
test_result_rec = "TestResult"
1564+
1565+
1566+
def process_test_function(self, device_name, conn, process):
1567+
builder.SetDeviceName(device_name)
1568+
1569+
rec = builder.longOut("TEST", initial_value=5)
1570+
1571+
# Record to indicate success/failure
1572+
bi = builder.boolIn(self.test_result_rec, ZNAM="FAILED", ONAM="SUCCESS")
1573+
1574+
builder.LoadDatabase()
1575+
softioc.iocInit()
1576+
1577+
# Prove value changes from .set call
1578+
rec.set(10, process=process)
1579+
1580+
conn.send("R") # "Ready"
1581+
log("CHILD: Sent R over Connection to Parent")
1582+
1583+
select_and_recv(conn, "R")
1584+
1585+
val = rec.get()
1586+
log(f"CHILD: record value is {val}")
1587+
1588+
# value should be that which was set by .set()
1589+
if val == 10:
1590+
bi.set(1)
1591+
else:
1592+
bi.set(0)
1593+
1594+
# Keep process alive while main thread works.
1595+
while (True):
1596+
if conn.poll(TIMEOUT):
1597+
val = conn.recv()
1598+
if val == "D": # "Done"
1599+
break
1600+
1601+
@requires_cothread
1602+
@pytest.mark.parametrize("process", [True, False])
1603+
def test_set_alarm_severity_status(self, process):
1604+
"""Test that set_alarm function allows setting severity and status"""
1605+
ctx = get_multiprocessing_context()
1606+
parent_conn, child_conn = ctx.Pipe()
1607+
1608+
device_name = create_random_prefix()
1609+
1610+
process = ctx.Process(
1611+
target=self.process_test_function,
1612+
args=(device_name, child_conn, process),
1613+
)
1614+
1615+
process.start()
1616+
1617+
from cothread.catools import caget, _channel_cache
1618+
from cothread import Sleep
1619+
1620+
try:
1621+
# Wait for message that IOC has started
1622+
select_and_recv(parent_conn, "R")
1623+
1624+
# Suppress potential spurious warnings
1625+
_channel_cache.purge()
1626+
1627+
record = device_name + ":TEST"
1628+
val = caget(record, timeout=TIMEOUT)
1629+
1630+
assert val == 10
1631+
1632+
parent_conn.send("R") # "Ready"
1633+
1634+
Sleep(0.5) # Give child time to process update
1635+
1636+
result_record = device_name + f":{self.test_result_rec}"
1637+
val = caget(result_record)
1638+
1639+
assert val == 1, "Success record indicates failure"
1640+
1641+
finally:
1642+
# Suppress potential spurious warnings
1643+
_channel_cache.purge()
1644+
parent_conn.send("D") # "Done"
1645+
process.join(timeout=TIMEOUT)

0 commit comments

Comments
 (0)