@@ -380,10 +380,12 @@ def out_records(self, request):
380380
381381 def validate_always_pass (self , record , new_val ):
382382 """Validation method that always allows changes"""
383+ log ("VALIDATE: Returning True" )
383384 return True
384385
385386 def validate_always_fail (self , record , new_val ):
386387 """Validation method that always rejects changes"""
388+ log ("VALIDATE: Returning False" )
387389 return False
388390
389391 def validate_ioc_test_func (
@@ -445,7 +447,6 @@ def validate_test_runner(
445447 # Wait for message that IOC has started
446448 select_and_recv (parent_conn , "R" )
447449
448-
449450 # Suppress potential spurious warnings
450451 _channel_cache .purge ()
451452
@@ -682,6 +683,95 @@ def test_on_update_true_false(self, out_records):
682683 always_update is True and the put'ed value is always different"""
683684 self .on_update_runner (out_records , True , False )
684685
686+ def on_update_recursive_set_test_func (
687+ self , device_name , conn
688+ ):
689+ log ("CHILD: Child started" )
690+
691+ builder .SetDeviceName (device_name )
692+
693+ async def on_update_func (new_val ):
694+ log ("CHILD: on_update_func started" )
695+ record .set (0 , process = False )
696+ conn .send ("C" ) # "Callback"
697+ log ("CHILD: on_update_func ended" )
698+
699+ record = builder .Action (
700+ "ACTION" ,
701+ on_update = on_update_func ,
702+ blocking = True ,
703+ initial_value = 1 # A non-zero value, to check it changes
704+ )
705+
706+ dispatcher = asyncio_dispatcher .AsyncioDispatcher ()
707+ builder .LoadDatabase ()
708+ softioc .iocInit (dispatcher )
709+
710+ conn .send ("R" ) # "Ready"
711+
712+ log ("CHILD: Sent R over Connection to Parent" )
713+
714+ # Keep process alive while main thread runs CAGET
715+ if conn .poll (TIMEOUT ):
716+ val = conn .recv ()
717+ assert val == "D" , "Did not receive expected Done character"
718+
719+ log ("CHILD: Received exit command, child exiting" )
720+
721+ async def test_on_update_recursive_set (self ):
722+ """Test that on_update functions correctly when the on_update
723+ callback sets the value of the record again (with process=False).
724+ See issue #201"""
725+
726+ ctx = get_multiprocessing_context ()
727+ parent_conn , child_conn = ctx .Pipe ()
728+
729+ device_name = create_random_prefix ()
730+
731+ process = ctx .Process (
732+ target = self .on_update_recursive_set_test_func ,
733+ args = (device_name , child_conn ),
734+ )
735+
736+ process .start ()
737+
738+ log ("PARENT: Child started, waiting for R command" )
739+
740+ from aioca import caget , caput
741+
742+ try :
743+ # Wait for message that IOC has started
744+ select_and_recv (parent_conn , "R" )
745+
746+ log ("PARENT: received R command" )
747+
748+ record = f"{ device_name } :ACTION"
749+
750+ val = await caget (record )
751+
752+ assert val == 1 , "ACTION record did not start with value 1"
753+
754+ await caput (record , 1 , wait = True )
755+
756+ val = await caget (record )
757+
758+ assert val == 0 , "ACTION record did not return to zero value"
759+
760+ # Expect one "C"
761+ select_and_recv (parent_conn , "C" )
762+
763+ # ...But if we receive another we know there's a problem
764+ if parent_conn .poll (5 ): # Shorter timeout to make this quicker
765+ pytest .fail ("Received unexpected second message" )
766+
767+ finally :
768+ log ("PARENT:Sending Done command to child" )
769+ parent_conn .send ("D" ) # "Done"
770+ process .join (timeout = TIMEOUT )
771+ log (f"PARENT: Join completed with exitcode { process .exitcode } " )
772+ if process .exitcode is None :
773+ pytest .fail ("Process did not terminate" )
774+
685775
686776
687777class TestBlocking :
@@ -1463,3 +1553,92 @@ def test_set_alarm_severity_status(self, set_enum):
14631553 _channel_cache .purge ()
14641554 parent_conn .send ("D" ) # "Done"
14651555 process .join (timeout = TIMEOUT )
1556+
1557+
1558+ class TestProcess :
1559+ """Tests related to processing - checking values are as expected
1560+ between the EPICS and Python layers. """
1561+
1562+ test_result_rec = "TestResult"
1563+
1564+
1565+ def process_test_function (self , device_name , conn , process ):
1566+ builder .SetDeviceName (device_name )
1567+
1568+ rec = builder .longOut ("TEST" , initial_value = 5 )
1569+
1570+ # Record to indicate success/failure
1571+ bi = builder .boolIn (self .test_result_rec , ZNAM = "FAILED" , ONAM = "SUCCESS" )
1572+
1573+ builder .LoadDatabase ()
1574+ softioc .iocInit ()
1575+
1576+ # Prove value changes from .set call
1577+ rec .set (10 , process = process )
1578+
1579+ conn .send ("R" ) # "Ready"
1580+ log ("CHILD: Sent R over Connection to Parent" )
1581+
1582+ select_and_recv (conn , "R" )
1583+
1584+ val = rec .get ()
1585+ log (f"CHILD: record value is { val } " )
1586+
1587+ # value should be that which was set by .set()
1588+ if val == 10 :
1589+ bi .set (1 )
1590+ else :
1591+ bi .set (0 )
1592+
1593+ # Keep process alive while main thread works.
1594+ while (True ):
1595+ if conn .poll (TIMEOUT ):
1596+ val = conn .recv ()
1597+ if val == "D" : # "Done"
1598+ break
1599+
1600+ @requires_cothread
1601+ @pytest .mark .parametrize ("process" , [True , False ])
1602+ def test_set_alarm_severity_status (self , process ):
1603+ """Test that set_alarm function allows setting severity and status"""
1604+ ctx = get_multiprocessing_context ()
1605+ parent_conn , child_conn = ctx .Pipe ()
1606+
1607+ device_name = create_random_prefix ()
1608+
1609+ process = ctx .Process (
1610+ target = self .process_test_function ,
1611+ args = (device_name , child_conn , process ),
1612+ )
1613+
1614+ process .start ()
1615+
1616+ from cothread .catools import caget , _channel_cache
1617+ from cothread import Sleep
1618+
1619+ try :
1620+ # Wait for message that IOC has started
1621+ select_and_recv (parent_conn , "R" )
1622+
1623+ # Suppress potential spurious warnings
1624+ _channel_cache .purge ()
1625+
1626+ record = device_name + ":TEST"
1627+ val = caget (record , timeout = TIMEOUT )
1628+
1629+ assert val == 10
1630+
1631+ parent_conn .send ("R" ) # "Ready"
1632+
1633+ Sleep (0.5 ) # Give child time to process update
1634+
1635+ result_record = device_name + f":{ self .test_result_rec } "
1636+ val = caget (result_record )
1637+
1638+ assert val == 1 , "Success record indicates failure"
1639+
1640+ finally :
1641+ # Suppress potential spurious warnings
1642+ _channel_cache .purge ()
1643+ parent_conn .send ("D" ) # "Done"
1644+ process .join (timeout = TIMEOUT )
0 commit comments