Skip to content

Commit 0b0af28

Browse files
Add on_update tests for issue #78
Also taking the opportunity to clear up some more warnings in some tests, and ensuring that code coverage is always enabled without having to explicitly call a function in every test.
1 parent 27bcc50 commit 0b0af28

File tree

3 files changed

+131
-13
lines changed

3 files changed

+131
-13
lines changed

tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,10 @@ def clear_records():
8383
yield
8484
_clear_records()
8585

86+
@pytest.fixture(autouse=True)
8687
def enable_code_coverage():
8788
"""Ensure code coverage works as expected for `multiprocesses` tests.
88-
This method should be called just before any `multiprocess.Process` call
89-
is made."""
89+
As its harmless for other types of test, we always run this fixture."""
9090
try:
9191
from pytest_cov.embed import cleanup_on_sigterm
9292
except ImportError:

tests/test_record_values.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from enum import Enum
77
from math import isnan, inf, nan
88

9-
from conftest import requires_cothread, enable_code_coverage
9+
from conftest import requires_cothread
1010

1111
from softioc import asyncio_dispatcher, builder, softioc
1212
from softioc.pythonSoftIoc import RecordWrapper
@@ -367,8 +367,6 @@ def run_test_function(
367367

368368
parent_conn, child_conn = multiprocessing.Pipe()
369369

370-
enable_code_coverage()
371-
372370
ioc_process = multiprocessing.Process(
373371
target=run_ioc,
374372
args=(record_configurations, child_conn, set_enum, get_enum),
@@ -781,8 +779,6 @@ def test_value_none_rejected_set_after_init(self, record_func_reject_none):
781779
exception"""
782780
queue = multiprocessing.Queue()
783781

784-
enable_code_coverage()
785-
786782
process = multiprocessing.Process(
787783
target=self.none_value_test_func,
788784
args=(record_func_reject_none, queue),

tests/test_records.py

Lines changed: 128 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pytest
55
import asyncio
66

7-
from conftest import requires_cothread, _clear_records, enable_code_coverage
7+
from conftest import requires_cothread, _clear_records
88

99
from 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

Comments
 (0)