def none_value_test_func(self, record_func, queue):
        """Start the IOC and catch the expected exception"""
        kwarg = {}
        if record_func in [builder.WaveformIn, builder.WaveformOut]:
            kwarg = {"length": WAVEFORM_LENGTH}  # Must specify when no value

        record = record_func("SOME-NAME", **kwarg)

        log("CHILD: About to start IOC")

        dispatcher = asyncio_dispatcher.AsyncioDispatcher()
        builder.LoadDatabase()
        softioc.iocInit(dispatcher)

        log("CHILD: Soft IOC started, about to .set(None)")

        try:
            record.set(None)
            log("CHILD: Uh-OH! No exception thrown when setting None!")
        except Exception as e:
            log("CHILD: Putting exception into queue %s", e)
            queue.put(e)
        else:
            log("CHILD: No exception raised when using None as value!")
            queue.put(Exception("FAIL: No exception raised during .set()"))
Example #2
0
 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)
Example #3
0
    def on_update_test_func(
        self, device_name, record_func, conn, always_update
    ):

        builder.SetDeviceName(device_name)

        li = builder.longIn("ON-UPDATE-COUNTER-RECORD", initial_value=0)

        def on_update_func(new_val):
            """Increments li record each time main out record receives caput"""
            li.set(li.get() + 1)

        kwarg = {}
        if record_func is builder.WaveformOut:
            kwarg = {"length": WAVEFORM_LENGTH}  # Must specify when no value

        record_func(
            "ON-UPDATE-RECORD",
            on_update=on_update_func,
            always_update=always_update,
            **kwarg)

        def on_update_done(_):
            conn.send("C")  # "Complete"
        # Put to the action record after we've done all other Puts, so we know
        # all the callbacks have finished processing
        builder.Action("ON-UPDATE-DONE", on_update=on_update_done)

        dispatcher = asyncio_dispatcher.AsyncioDispatcher()
        builder.LoadDatabase()
        softioc.iocInit(dispatcher)

        conn.send("R")  # "Ready"

        log("CHILD: Sent R over Connection to Parent")

        # Keep process alive while main thread runs CAGET
        if conn.poll(TIMEOUT):
            val = conn.recv()
            assert val == "D", "Did not receive expected Done character"

        log("CHILD: Received exit command, child exiting")
Example #4
0
    def blocking_test_func(self, device_name, conn):

        builder.SetDeviceName(device_name)

        count_rec = builder.longIn("BLOCKING-COUNTER", initial_value=0)

        async def blocking_update_func(new_val):
            """A function that will block for some time"""
            log("CHILD: blocking_update_func starting")
            await asyncio.sleep(0.5)
            log("CHILD: Finished sleep!")
            completed_count = count_rec.get() + 1
            count_rec.set(completed_count)
            log(
                "CHILD: blocking_update_func finished, completed ",
                completed_count
            )

        builder.longOut(
            "BLOCKING-REC",
            on_update=blocking_update_func,
            always_update=True,
            blocking=True
        )


        dispatcher = asyncio_dispatcher.AsyncioDispatcher()
        builder.LoadDatabase()
        softioc.iocInit(dispatcher)

        conn.send("R")  # "Ready"

        log("CHILD: Sent R over Connection to Parent")

        # Keep process alive while main thread runs CAGET
        if conn.poll(TIMEOUT):
            val = conn.recv()
            assert val == "D", "Did not receive expected Done character"

        log("CHILD: Received exit command, child exiting")
    def test_value_none_rejected_set_after_init(self, record_func_reject_none):
        """Test that setting \"None\" using .set() after IOC init raises an
        exception"""
        queue = multiprocessing.Queue()

        process = multiprocessing.Process(
            target=self.none_value_test_func,
            args=(record_func_reject_none, queue),
        )

        process.start()

        log("PARENT: Child process started, waiting for returned exception")

        try:
            exception = queue.get(timeout=TIMEOUT)

            assert isinstance(exception, self.expected_exceptions)
        finally:
            log("PARENT: Issuing terminate to child process")
            process.terminate()
            process.join(timeout=TIMEOUT)
            if process.exitcode is None:
                pytest.fail("Process did not terminate")
Example #6
0
 async def blocking_update_func(new_val):
     """A function that will block for some time"""
     log("CHILD: blocking_update_func starting")
     await asyncio.sleep(0.5)
     log("CHILD: Finished sleep!")
     completed_count = count_rec.get() + 1
     count_rec.set(completed_count)
     log(
         "CHILD: blocking_update_func finished, completed ",
         completed_count
     )
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 #8
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 #9
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 #10
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")