async def test_asyncio_ioc_override(asyncio_ioc_override): from aioca import caget, caput with Listener(ADDRESS) as listener, listener.accept() as conn: select_and_recv(conn, "R") # "Ready" # Gain bo pre = asyncio_ioc_override.pv_prefix assert (await caget(pre + ":GAIN")) == 0 await caput(pre + ":GAIN", "On", wait=True) assert (await caget(pre + ":GAIN")) == 1 # Stop await caput(pre + ":SYSRESET", 1) conn.send("D") # "Done" select_and_recv(conn, "D") # "Done" # check closed and output out, err = asyncio_ioc_override.proc.communicate(timeout=5) out = out.decode() err = err.decode() # check closed and output try: assert '1' in out assert 'Starting iocInit' in err assert 'iocRun: All initialization complete' in err assert 'IOC reboot started' in err except Exception: # Useful printing for when things go wrong! print("Out:", out) print("Err:", err) raise
def validate_test_runner( self, creation_func, new_value, expected_value, validate_pass: bool): parent_conn, child_conn = multiprocessing.Pipe() device_name = create_random_prefix() process = multiprocessing.Process( target=self.validate_ioc_test_func, args=(device_name, creation_func, child_conn, validate_pass), ) process.start() from cothread.catools import caget, caput, _channel_cache try: # Wait for message that IOC has started select_and_recv(parent_conn, "R") # Suppress potential spurious warnings _channel_cache.purge() kwargs = {} if creation_func in [builder.longStringIn, builder.longStringOut]: from cothread.dbr import DBR_CHAR_STR kwargs.update({"datatype": DBR_CHAR_STR}) put_ret = caput( device_name + ":VALIDATE-RECORD", new_value, wait=True, **kwargs, ) assert put_ret.ok, "caput did not succeed" ret_val = caget( device_name + ":VALIDATE-RECORD", timeout=TIMEOUT, **kwargs ) if creation_func in [builder.WaveformOut, builder.WaveformIn]: assert numpy.array_equal(ret_val, expected_value) else: assert ret_val == expected_value finally: # Suppress potential spurious warnings _channel_cache.purge() parent_conn.send("D") # "Done" process.join(timeout=TIMEOUT)
def test_cothread_ioc(cothread_ioc): import cothread from cothread.catools import ca_nothing, caget, caput, camonitor pre = cothread_ioc.pv_prefix with Listener(ADDRESS) as listener, listener.accept() as conn: select_and_recv(conn, "R") # "Ready" # Start assert caget(pre + ":UPTIME").startswith("00:00:0") # WAVEFORM caput(pre + ":SINN", 4, wait=True) q = cothread.EventQueue() m = camonitor(pre + ":SIN", q.Signal, notify_disconnect=True) assert len(q.Wait(1)) == 4 # STRINGOUT assert caget(pre + ":STRINGOUT") == "watevah" caput(pre + ":STRINGOUT", "something", wait=True) assert caget(pre + ":STRINGOUT") == "something" # Check pvaccess works from p4p.client.cothread import Context with Context("pva") as ctx: assert ctx.get(pre + ":STRINGOUT") == "something" conn.send("D") # "Done" select_and_recv(conn, "D") # "Done" # Stop cothread_ioc.proc.send_signal(signal.SIGINT) # Disconnect assert isinstance(q.Wait(10), ca_nothing) m.close() # check closed and output out, err = cothread_ioc.proc.communicate() out = out.decode() err = err.decode() # check closed and output try: assert "%s:SINN.VAL 1024 -> 4" % pre in out assert 'update_sin_wf 4' in out assert "%s:STRINGOUT.VAL watevah -> something" % pre in out assert 'on_update \'something\'' in out assert 'Starting iocInit' in err assert 'iocRun: All initialization complete' in err except Exception: # Useful printing for when things go wrong! print("Out:", out) print("Err:", err) raise
from softioc import softioc, builder, pvlog from conftest import ADDRESS, select_and_recv if __name__ == "__main__": with Client(ADDRESS) as conn: import cothread # Being run as an IOC, so parse args and set prefix parser = ArgumentParser() parser.add_argument('prefix', help="The PV prefix for the records") parsed_args = parser.parse_args() builder.SetDeviceName(parsed_args.prefix) import sim_records # Run the IOC builder.LoadDatabase() softioc.iocInit() conn.send("R") # "Ready" select_and_recv(conn, "D") # "Done" # Attempt to ensure all buffers flushed - C code (from `import pvlog`) # may not be affected by these calls... sys.stdout.flush() sys.stderr.flush() conn.send("D") # "Ready"
def run_test_function(record_configurations: list, set_enum: SetValueEnum, get_enum: GetValueEnum): """Run the test function using multiprocessing and check returned value is expected value. set_enum and get_enum determine when the record's value is set and how the value is retrieved, respectively.""" parent_conn, child_conn = multiprocessing.Pipe() ioc_process = multiprocessing.Process( target=run_ioc, args=(record_configurations, child_conn, set_enum, get_enum), ) ioc_process.start() # Wait for message that IOC has started select_and_recv(parent_conn, "R") # Cannot do these imports before the subprocess starts, as the subprocess # would inherit cothread's internal state which would break things! from cothread import Yield from cothread.catools import caget, caput, _channel_cache from cothread.dbr import DBR_CHAR_STR try: # cothread remembers connected IOCs. As we potentially restart the same # named IOC multiple times, we have to purge the cache else the # result from caget/caput cache would be a DisconnectError during the # second test _channel_cache.purge() for configuration in record_configurations: ( record_name, creation_func, initial_value, expected_value, expected_type, ) = configuration # Infer some required keywords from parameters kwargs = {} put_kwarg = {} if creation_func in [builder.longStringIn, builder.longStringOut]: kwargs["datatype"] = DBR_CHAR_STR if (creation_func in [builder.WaveformIn, builder.WaveformOut] and type(initial_value) is bytes): # There's a bug in caput that means DBR_CHAR_STR doesn't # truncate the array of the target record, meaning .get() # returns all NELM rather than just NORD elements. Instead we # encode the data ourselves initial_value = numpy.frombuffer(initial_value, dtype=numpy.uint8) if set_enum == SetValueEnum.CAPUT: if get_enum == GetValueEnum.GET: select_and_recv(parent_conn) caput( DEVICE_NAME + ":" + record_name, initial_value, wait=True, **kwargs, **put_kwarg, ) if get_enum == GetValueEnum.GET: parent_conn.send("G") # "Get" # Ensure IOC process has time to execute. # I saw failures on MacOS where it appeared the IOC had not # processed the put'ted value as the caget returned the same # value as was originally passed in. Yield(timeout=TIMEOUT) if get_enum == GetValueEnum.GET: rec_val = select_and_recv(parent_conn) else: rec_val = caget( DEVICE_NAME + ":" + record_name, timeout=TIMEOUT, **kwargs, ) # '+' operator used to convert cothread's types into Python # native types e.g. "+ca_int" -> int rec_val = +rec_val if (creation_func in [builder.WaveformOut, builder.WaveformIn] and expected_value.dtype in [numpy.float64, numpy.int32]): log("caget cannot distinguish between a waveform with 1 " "element and a scalar value, and so always returns a " "scalar. Therefore we skip this check.") continue record_value_asserts(creation_func, rec_val, expected_value, expected_type) finally: # Purge cache to suppress spurious "IOC disconnected" exceptions _channel_cache.purge() parent_conn.send("D") # "Done" ioc_process.join(timeout=TIMEOUT) if ioc_process.exitcode is None: pytest.fail("Process did not terminate")
async def test_blocking_multiple_threads(self): """Test that a blocking record correctly causes caputs from multiple threads to wait for the expected time""" parent_conn, child_conn = multiprocessing.Pipe() device_name = create_random_prefix() process = multiprocessing.Process( target=self.blocking_test_func, args=(device_name, child_conn), ) process.start() log("PARENT: Child started, waiting for R command") from aioca import caget, caput try: # Wait for message that IOC has started select_and_recv(parent_conn, "R") log("PARENT: received R command") MAX_COUNT = 4 async def query_record(index): log("SPAWNED: beginning blocking caput ", index) await caput( device_name + ":BLOCKING-REC", 5, # Arbitrary value wait=True, timeout=TIMEOUT ) log("SPAWNED: caput complete ", index) queries = [query_record(i) for i in range(MAX_COUNT)] * MAX_COUNT log("PARENT: Gathering list of queries") await asyncio.gather(*queries) log("PARENT: Getting value from counter") ret_val = await caget( device_name + ":BLOCKING-COUNTER", timeout=TIMEOUT, ) assert ret_val.ok, \ f"caget did not succeed: {ret_val.errorcode}, {ret_val}" log(f"PARENT: Received val from COUNTER: {ret_val}") assert ret_val == MAX_COUNT finally: # Clear the cache before stopping the IOC stops # "channel disconnected" error messages aioca_cleanup() log("PARENT: Sending Done command to child") parent_conn.send("D") # "Done" process.join(timeout=TIMEOUT) log(f"PARENT: Join completed with exitcode {process.exitcode}") if process.exitcode is None: pytest.fail("Process did not terminate")
def test_blocking_single_thread_multiple_calls(self): """Test that a blocking record correctly causes multiple caputs from a single thread to wait for the expected time""" parent_conn, child_conn = multiprocessing.Pipe() device_name = create_random_prefix() process = multiprocessing.Process( target=self.blocking_test_func, args=(device_name, child_conn), ) process.start() log("PARENT: Child started, waiting for R command") from cothread.catools import caget, caput, _channel_cache try: # Wait for message that IOC has started select_and_recv(parent_conn, "R") log("PARENT: received R command") # Suppress potential spurious warnings _channel_cache.purge() # Track number of puts sent count = 1 MAX_COUNT = 4 log("PARENT: begining While loop") while count <= MAX_COUNT: put_ret = caput( device_name + ":BLOCKING-REC", 5, # Arbitrary value wait=True, timeout=TIMEOUT ) assert put_ret.ok, f"caput did not succeed: {put_ret.errorcode}" log(f"PARENT: completed caput with count {count}") count += 1 log("PARENT: Getting value from counter") ret_val = caget( device_name + ":BLOCKING-COUNTER", timeout=TIMEOUT, ) assert ret_val.ok, \ f"caget did not succeed: {ret_val.errorcode}, {ret_val}" log(f"PARENT: Received val from COUNTER: {ret_val}") assert ret_val == MAX_COUNT finally: # Suppress potential spurious warnings _channel_cache.purge() log("PARENT: Sending Done command to child") parent_conn.send("D") # "Done" process.join(timeout=TIMEOUT) log(f"PARENT: Join completed with exitcode {process.exitcode}") if process.exitcode is None: pytest.fail("Process did not terminate")
def on_update_runner(self, creation_func, always_update, put_same_value): parent_conn, child_conn = multiprocessing.Pipe() device_name = create_random_prefix() process = multiprocessing.Process( target=self.on_update_test_func, args=(device_name, creation_func, child_conn, always_update), ) process.start() log("PARENT: Child started, waiting for R command") from cothread.catools import caget, caput, _channel_cache try: # Wait for message that IOC has started select_and_recv(parent_conn, "R") log("PARENT: received R command") # Suppress potential spurious warnings _channel_cache.purge() # Use this number to put to records - don't start at 0 as many # record types default to 0 and we usually want to put a different # value to force processing to occur count = 1 log("PARENT: begining While loop") while count < 4: put_ret = caput( device_name + ":ON-UPDATE-RECORD", 9 if put_same_value else count, wait=True, ) assert put_ret.ok, f"caput did not succeed: {put_ret.errorcode}" log(f"PARENT: completed caput with count {count}") count += 1 log("PARENT: Put'ing to DONE record") caput( device_name + ":ON-UPDATE-DONE", 1, wait=True, ) log("PARENT: Waiting for C command") # Wait for action record to process, so we know all the callbacks # have finished processing (This assumes record callbacks are not # re-ordered, and will run in the same order as the caputs we sent) select_and_recv(parent_conn, "C") log("PARENT: Received C command") ret_val = caget( device_name + ":ON-UPDATE-COUNTER-RECORD", timeout=TIMEOUT, ) assert ret_val.ok, \ f"caget did not succeed: {ret_val.errorcode}, {ret_val}" log(f"PARENT: Received val from COUNTER: {ret_val}") # Expected value is either 3 (incremented once per caput) # or 1 (incremented on first caput and not subsequent ones) expected_val = 3 if put_same_value and not always_update: expected_val = 1 assert ret_val == expected_val finally: # Suppress potential spurious warnings _channel_cache.purge() log("PARENT:Sending Done command to child") parent_conn.send("D") # "Done" process.join(timeout=TIMEOUT) log(f"PARENT: Join completed with exitcode {process.exitcode}") if process.exitcode is None: pytest.fail("Process did not terminate")
async def test_asyncio_ioc(asyncio_ioc): import asyncio from aioca import caget, caput, camonitor, CANothing, FORMAT_TIME pre = asyncio_ioc.pv_prefix with Listener(ADDRESS) as listener, listener.accept() as conn: select_and_recv(conn, "R") # "Ready" # Start assert (await caget(pre + ":UPTIME")).startswith("00:00:0") # WAVEFORM await caput(pre + ":SINN", 4, wait=True) q = asyncio.Queue() m = camonitor(pre + ":SIN", q.put, notify_disconnect=True) assert len(await asyncio.wait_for(q.get(), 1)) == 4 # AO ao_val = await caget(pre + ":ALARM", format=FORMAT_TIME) assert ao_val == 0 assert ao_val.severity == 3 # INVALID assert ao_val.status == 17 # UDF ai_val = await caget(pre + ":AI", format=FORMAT_TIME) assert ai_val == 23.45 assert ai_val.severity == 0 assert ai_val.status == 0 await caput(pre + ":ALARM", 3, wait=True) await caput(pre + ":NAME-CALLBACK", 12, wait=True) # Confirm the ALARM callback has completed select_and_recv(conn, "C") # "Complete" ai_val = await caget(pre + ":AI", format=FORMAT_TIME) assert ai_val == 23.45 assert ai_val.severity == 3 assert ai_val.status == 7 # STATE_ALARM # Check pvaccess works from p4p.client.asyncio import Context with Context("pva") as ctx: assert await ctx.get(pre + ":AI") == 23.45 conn.send("D") # "Done" select_and_recv(conn, "D") # "Done" # Stop out, err = asyncio_ioc.proc.communicate(b"exit\n", timeout=5) out = out.decode() err = err.decode() # Disconnect assert isinstance(await asyncio.wait_for(q.get(), 10), CANothing) m.close() # check closed and output try: assert "%s:SINN.VAL 1024 -> 4" % pre in out assert 'update_sin_wf 4' in out assert "%s:ALARM.VAL 0 -> 3" % pre in out assert 'on_update %s:AO : 3.0' % pre in out assert 'async update 3.0 (23.45)' in out assert "%s:NAME-CALLBACK value 12" % pre in out assert 'Starting iocInit' in err assert 'iocRun: All initialization complete' in err except Exception: # Useful printing for when things go wrong! print("Out:", out) print("Err:", err) raise