|
12 | 12 | log,
|
13 | 13 | select_and_recv,
|
14 | 14 | TIMEOUT,
|
15 |
| - get_multiprocessing_context |
| 15 | + get_multiprocessing_context, |
| 16 | + create_random_prefix |
16 | 17 | )
|
17 | 18 |
|
18 | 19 | from softioc import asyncio_dispatcher, builder, softioc
|
| 20 | +from softioc.fields import DBR_STRING |
19 | 21 | from softioc.pythonSoftIoc import RecordWrapper
|
20 | 22 |
|
21 | 23 | # Test file for anything related to valid record values getting/setting
|
@@ -966,3 +968,136 @@ def test_mbb_rejects_invalid_values(self):
|
966 | 968 | builder.mbbIn("MBB_IN_3", initial_value=16)
|
967 | 969 | with pytest.raises(AssertionError):
|
968 | 970 | builder.mbbOut("MBB_OUT_3", initial_value=16)
|
| 971 | + |
| 972 | + def invalid_value_test_func(self, device_name, conn, creation_func): |
| 973 | + |
| 974 | + builder.SetDeviceName(device_name) |
| 975 | + |
| 976 | + creation_func("INVALID_VAL_REC") |
| 977 | + |
| 978 | + dispatcher = asyncio_dispatcher.AsyncioDispatcher() |
| 979 | + builder.LoadDatabase() |
| 980 | + softioc.iocInit(dispatcher) |
| 981 | + |
| 982 | + conn.send("R") # "Ready" |
| 983 | + |
| 984 | + log("CHILD: Sent R over Connection to Parent") |
| 985 | + |
| 986 | + # Keep process alive while main thread runs CAGET |
| 987 | + if conn.poll(TIMEOUT): |
| 988 | + val = conn.recv() |
| 989 | + assert val == "D", "Did not receive expected Done character" |
| 990 | + |
| 991 | + log("CHILD: Received exit command, child exiting") |
| 992 | + |
| 993 | + @requires_cothread |
| 994 | + @pytest.mark.parametrize( |
| 995 | + "creation_func, invalid_min, invalid_max, expected_val", |
| 996 | + [ |
| 997 | + ( |
| 998 | + builder.longOut, |
| 999 | + numpy.iinfo(numpy.int32).min - 1, |
| 1000 | + numpy.iinfo(numpy.int32).max + 1, |
| 1001 | + 0, |
| 1002 | + ), |
| 1003 | + ( |
| 1004 | + builder.boolOut, |
| 1005 | + -1, |
| 1006 | + 2, |
| 1007 | + 0, |
| 1008 | + ), |
| 1009 | + ( |
| 1010 | + builder.mbbOut, |
| 1011 | + -1, |
| 1012 | + 16, |
| 1013 | + 0, |
| 1014 | + ), |
| 1015 | + ], |
| 1016 | +
|
| 1017 | + ) |
| 1018 | + def test_invalid_values_caput( |
| 1019 | + self, creation_func, invalid_min, invalid_max, expected_val |
| 1020 | + ): |
| 1021 | + """Test that attempting to set invalid values causes caput to return |
| 1022 | + an error, and the record's value remains unchanged.""" |
| 1023 | + if creation_func == builder.mbbOut: |
| 1024 | + pytest.skip( |
| 1025 | + "Bug somewhere, possibly Cothread, means that errors are not " |
| 1026 | + "reported for mbbOut records") |
| 1027 | + |
| 1028 | + ctx = get_multiprocessing_context() |
| 1029 | + |
| 1030 | + parent_conn, child_conn = ctx.Pipe() |
| 1031 | + |
| 1032 | + device_name = create_random_prefix() |
| 1033 | + |
| 1034 | + process = ctx.Process( |
| 1035 | + target=self.invalid_value_test_func, |
| 1036 | + args=(device_name, child_conn, creation_func), |
| 1037 | + ) |
| 1038 | + |
| 1039 | + process.start() |
| 1040 | + |
| 1041 | + log("PARENT: Child started, waiting for R command") |
| 1042 | + |
| 1043 | + from cothread.catools import caget, caput, _channel_cache |
| 1044 | + |
| 1045 | + try: |
| 1046 | + # Wait for message that IOC has started |
| 1047 | + select_and_recv(parent_conn, "R") |
| 1048 | + |
| 1049 | + log("PARENT: received R command") |
| 1050 | + |
| 1051 | + # Suppress potential spurious warnings |
| 1052 | + _channel_cache.purge() |
| 1053 | + |
| 1054 | + record_name = device_name + ":INVALID_VAL_REC" |
| 1055 | + |
| 1056 | + log(f"PARENT: Putting invalid min value {invalid_min} to record") |
| 1057 | + # Send as string data, otherwise catools will automatically convert |
| 1058 | + # it to a valid int32 |
| 1059 | + put_ret = caput( |
| 1060 | + record_name, |
| 1061 | + invalid_min, |
| 1062 | + wait=True, |
| 1063 | + datatype=DBR_STRING, |
| 1064 | + throw=False, |
| 1065 | + ) |
| 1066 | + assert not put_ret.ok, \ |
| 1067 | + "caput for invalid minimum value unexpectedly succeeded" |
| 1068 | + |
| 1069 | + log(f"PARENT: Putting invalid max value {invalid_max} to record") |
| 1070 | + put_ret = caput( |
| 1071 | + record_name, |
| 1072 | + invalid_max, |
| 1073 | + wait=True, |
| 1074 | + datatype=DBR_STRING, |
| 1075 | + throw=False, |
| 1076 | + ) |
| 1077 | + assert not put_ret.ok, \ |
| 1078 | + "caput for invalid maximum value unexpectedly succeeded" |
| 1079 | + |
| 1080 | + |
| 1081 | + log("PARENT: Getting value from record") |
| 1082 | + |
| 1083 | + ret_val = caget( |
| 1084 | + record_name, |
| 1085 | + timeout=TIMEOUT, |
| 1086 | + ) |
| 1087 | + assert ret_val.ok, \ |
| 1088 | + f"caget did not succeed: {ret_val.errorcode}, {ret_val}" |
| 1089 | + |
| 1090 | + log(f"PARENT: Received val from record: {ret_val}") |
| 1091 | + |
| 1092 | + assert ret_val == expected_val |
| 1093 | + |
| 1094 | + finally: |
| 1095 | + # Suppress potential spurious warnings |
| 1096 | + _channel_cache.purge() |
| 1097 | + |
| 1098 | + log("PARENT: Sending Done command to child") |
| 1099 | + parent_conn.send("D") # "Done" |
| 1100 | + process.join(timeout=TIMEOUT) |
| 1101 | + log(f"PARENT: Join completed with exitcode {process.exitcode}") |
| 1102 | + if process.exitcode is None: |
| 1103 | + pytest.fail("Process did not terminate") |
0 commit comments