44import pytest
55import asyncio
66
7- from conftest import requires_cothread , _clear_records , enable_code_coverage
7+ from conftest import requires_cothread , _clear_records
88
99from softioc import asyncio_dispatcher , builder , softioc
1010
@@ -179,8 +179,6 @@ def validate_test_runner(
179179
180180 queue = multiprocessing .Queue ()
181181
182- enable_code_coverage ()
183-
184182 process = multiprocessing .Process (
185183 target = self .validate_ioc_test_func ,
186184 args = (creation_func , queue , validate_pass ),
@@ -193,7 +191,7 @@ def validate_test_runner(
193191
194192 from cothread .catools import caget , caput , _channel_cache
195193
196- # See other places in this file for why we call it
194+ # Suppress potential spurious warnings
197195 _channel_cache .purge ()
198196
199197 kwargs = {}
@@ -202,15 +200,15 @@ def validate_test_runner(
202200 kwargs .update ({"datatype" : DBR_CHAR_STR })
203201
204202 put_ret = caput (
205- DEVICE_NAME + ":" + " VALIDATE-RECORD" ,
203+ DEVICE_NAME + ":VALIDATE-RECORD" ,
206204 new_value ,
207205 wait = True ,
208206 ** kwargs ,
209207 )
210208 assert put_ret .ok , "caput did not succeed"
211209
212210 ret_val = caget (
213- DEVICE_NAME + ":" + " VALIDATE-RECORD" ,
211+ DEVICE_NAME + ":VALIDATE-RECORD" ,
214212 timeout = 3 ,
215213 ** kwargs
216214 )
@@ -221,6 +219,8 @@ def validate_test_runner(
221219 assert ret_val == expected_value
222220
223221 finally :
222+ # Suppress potential spurious warnings
223+ _channel_cache .purge ()
224224 queue .put ("EXIT" )
225225 process .join (timeout = 3 )
226226
@@ -243,3 +243,125 @@ def test_validate_blocks_updates(self, out_records):
243243 creation_func , value , default = out_records
244244
245245 self .validate_test_runner (creation_func , value , default , False )
246+
247+ class TestOnUpdate :
248+ """Tests related to the on_update callback, with and without
249+ always_update set"""
250+
251+ @pytest .fixture (
252+ params = [
253+ builder .aOut ,
254+ builder .boolOut ,
255+ # builder.Action, This is just boolOut + always_update
256+ builder .longOut ,
257+ builder .stringOut ,
258+ builder .mbbOut ,
259+ builder .WaveformOut ,
260+ builder .longStringOut ,
261+ ]
262+ )
263+ def out_records (self , request ):
264+ """The list of Out records to test """
265+ return request .param
266+
267+ def on_update_test_func (self , record_func , queue , always_update ):
268+
269+ def on_update_func (new_val ):
270+ """Increments li record each time main out record receives caput"""
271+ nonlocal li
272+ li .set (li .get () + 1 )
273+
274+ builder .SetDeviceName (DEVICE_NAME )
275+
276+ kwarg = {}
277+ if record_func is builder .WaveformOut :
278+ kwarg = {"length" : 50 } # Required when no value on creation
279+
280+ record_func (
281+ "ON-UPDATE-RECORD" ,
282+ on_update = on_update_func ,
283+ always_update = always_update ,
284+ ** kwarg )
285+
286+ li = builder .longIn ("ON-UPDATE-COUNTER-RECORD" , initial_value = 0 )
287+
288+ dispatcher = asyncio_dispatcher .AsyncioDispatcher ()
289+ builder .LoadDatabase ()
290+ softioc .iocInit (dispatcher )
291+
292+ queue .put ("IOC ready" )
293+
294+ # Keep process alive while main thread runs CAGET
295+ queue .get (timeout = TIMEOUT )
296+
297+ def on_update_runner (self , creation_func , always_update , put_same_value ):
298+ queue = multiprocessing .Queue ()
299+
300+ process = multiprocessing .Process (
301+ target = self .on_update_test_func ,
302+ args = (creation_func , queue , always_update ),
303+ )
304+
305+ process .start ()
306+
307+ try :
308+ queue .get (timeout = 5 ) # Get the expected IOC initialised message
309+
310+ from cothread .catools import caget , caput , _channel_cache
311+
312+ # Suppress potential spurious warnings
313+ _channel_cache .purge ()
314+
315+ # Use this number to put to records - don't start at 0 as many
316+ # record types default to 0 and we usually want to put a different
317+ # value to force processing to occur
318+ count = 1
319+
320+ while count < 4 :
321+ put_ret = caput (
322+ DEVICE_NAME + ":ON-UPDATE-RECORD" ,
323+ 9 if put_same_value else count ,
324+ wait = True ,
325+ )
326+ assert put_ret .ok , "caput did not succeed"
327+ count += 1
328+
329+ ret_val = caget (
330+ DEVICE_NAME + ":ON-UPDATE-COUNTER-RECORD" ,
331+ timeout = 3 ,
332+ )
333+
334+ # Expected value is either 3 (incremented once per caput)
335+ # or 1 (incremented on first caput and not subsequent ones)
336+ expected_val = 3
337+ if put_same_value and not always_update :
338+ expected_val = 1
339+
340+ assert ret_val == expected_val
341+
342+ finally :
343+ # Suppress potential spurious warnings
344+ _channel_cache .purge ()
345+ queue .put ("EXIT" )
346+ process .join (timeout = 3 )
347+
348+ @requires_cothread
349+ def test_on_update_false_false (self , out_records ):
350+ """Test that on_update works correctly for all out records when
351+ always_update is False and the put'ed value is always different"""
352+ self .on_update_runner (out_records , False , False )
353+
354+ def test_on_update_false_true (self , out_records ):
355+ """Test that on_update works correctly for all out records when
356+ always_update is False and the put'ed value is always the same"""
357+ self .on_update_runner (out_records , False , True )
358+
359+ def test_on_update_true_true (self , out_records ):
360+ """Test that on_update works correctly for all out records when
361+ always_update is True and the put'ed value is always the same"""
362+ self .on_update_runner (out_records , True , True )
363+
364+ def test_on_update_true_false (self , out_records ):
365+ """Test that on_update works correctly for all out records when
366+ always_update is True and the put'ed value is always different"""
367+ self .on_update_runner (out_records , True , False )
0 commit comments