Example #1
0
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
Example #2
0
    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)
Example #3
0
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")
Example #6
0
    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")
Example #7
0
    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")
Example #8
0
    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")
Example #9
0
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