@@ -904,3 +904,197 @@ async def query_record(index):
904904 log (f"PARENT: Join completed with exitcode { process .exitcode } " )
905905 if process .exitcode is None :
906906 pytest .fail ("Process did not terminate" )
907+
908+ class TestGetSetField :
909+ """Tests related to get_field and set_field on records"""
910+
911+ test_result_rec = "TestResult"
912+
913+ def test_set_field_before_init_fails (self ):
914+ """Test that calling set_field before iocInit() raises an exception"""
915+
916+ ao = builder .aOut ("testAOut" )
917+
918+ with pytest .raises (AssertionError ) as e :
919+ ao .set_field ("EGU" , "Deg" )
920+
921+ assert "set_field may only be called after iocInit" in str (e .value )
922+
923+ def test_get_field_before_init_fails (self ):
924+ """Test that calling get_field before iocInit() raises an exception"""
925+
926+ ao = builder .aOut ("testAOut" )
927+
928+ with pytest .raises (AssertionError ) as e :
929+ ao .get_field ("EGU" )
930+
931+ assert "get_field may only be called after iocInit" in str (e .value )
932+
933+ def get_set_test_func (self , device_name , conn ):
934+ """Run an IOC and do simple get_field/set_field calls"""
935+
936+ builder .SetDeviceName (device_name )
937+
938+ lo = builder .longOut ("TestLongOut" , EGU = "unset" , DRVH = 12 )
939+
940+ # Record to indicate success/failure
941+ bi = builder .boolIn (self .test_result_rec , ZNAM = "FAILED" , ONAM = "SUCCESS" )
942+
943+ dispatcher = asyncio_dispatcher .AsyncioDispatcher ()
944+ builder .LoadDatabase ()
945+ softioc .iocInit (dispatcher )
946+
947+ conn .send ("R" ) # "Ready"
948+
949+ log ("CHILD: Sent R over Connection to Parent" )
950+
951+ # Set and then get the EGU field
952+ egu = "TEST"
953+ lo .set_field ("EGU" , egu )
954+ log ("CHILD: set_field successful" )
955+ readback_egu = lo .get_field ("EGU" )
956+ log (f"CHILD: get_field returned { readback_egu } " )
957+ assert readback_egu == egu , f"EGU field was not { egu } , was { readback_egu } "
958+ log (f"CHILD: assert passed" )
959+
960+ # Test completed, report to listening camonitor
961+ bi .set (True )
962+
963+ # Keep process alive while main thread works.
964+ while (True ):
965+ if conn .poll (TIMEOUT ):
966+ val = conn .recv ()
967+ if val == "D" : # "Done"
968+ break
969+
970+ log ("CHILD: Received exit command, child exiting" )
971+
972+
973+ @pytest .mark .asyncio
974+ async def test_get_set (self ):
975+ """Test a simple set_field/get_field is successful"""
976+ ctx = get_multiprocessing_context ()
977+ parent_conn , child_conn = ctx .Pipe ()
978+
979+ device_name = create_random_prefix ()
980+
981+ process = ctx .Process (
982+ target = self .get_set_test_func ,
983+ args = (device_name , child_conn ),
984+ )
985+
986+ process .start ()
987+
988+ log ("PARENT: Child started, waiting for R command" )
989+
990+ from aioca import camonitor
991+
992+ try :
993+ # Wait for message that IOC has started
994+ select_and_recv (parent_conn , "R" )
995+
996+ log ("PARENT: received R command" )
997+
998+ queue = asyncio .Queue ()
999+ record = device_name + ":" + self .test_result_rec
1000+ monitor = camonitor (record , queue .put )
1001+
1002+ log (f"PARENT: monitoring { record } " )
1003+ new_val = await asyncio .wait_for (queue .get (), TIMEOUT )
1004+ log (f"PARENT: new_val is { new_val } " )
1005+ assert new_val == True , "Test failed, value was not True"
1006+
1007+ finally :
1008+ monitor .close ()
1009+ # Clear the cache before stopping the IOC stops
1010+ # "channel disconnected" error messages
1011+ aioca_cleanup ()
1012+
1013+ log ("PARENT: Sending Done command to child" )
1014+ parent_conn .send ("D" ) # "Done"
1015+ process .join (timeout = TIMEOUT )
1016+ log (f"PARENT: Join completed with exitcode { process .exitcode } " )
1017+ if process .exitcode is None :
1018+ pytest .fail ("Process did not terminate" )
1019+
1020+ def get_set_too_long_value (self , device_name , conn ):
1021+ """Run an IOC and deliberately call set_field with a too-long value"""
1022+
1023+ builder .SetDeviceName (device_name )
1024+
1025+ lo = builder .longOut ("TestLongOut" , EGU = "unset" , DRVH = 12 )
1026+
1027+ # Record to indicate success/failure
1028+ bi = builder .boolIn (self .test_result_rec , ZNAM = "FAILED" , ONAM = "SUCCESS" )
1029+
1030+ dispatcher = asyncio_dispatcher .AsyncioDispatcher ()
1031+ builder .LoadDatabase ()
1032+ softioc .iocInit (dispatcher )
1033+
1034+ conn .send ("R" ) # "Ready"
1035+
1036+ log ("CHILD: Sent R over Connection to Parent" )
1037+
1038+ # Set a too-long value and confirm it reports an error
1039+ try :
1040+ lo .set_field ("EGU" , "ThisStringIsFarTooLongToFitIntoTheEguField" )
1041+ except ValueError as e :
1042+ # Expected error, report success to listening camonitor
1043+ assert "byte string too long" in e .args [0 ]
1044+ bi .set (True )
1045+
1046+ # Keep process alive while main thread works.
1047+ while (True ):
1048+ if conn .poll (TIMEOUT ):
1049+ val = conn .recv ()
1050+ if val == "D" : # "Done"
1051+ break
1052+
1053+ log ("CHILD: Received exit command, child exiting" )
1054+
1055+ @pytest .mark .asyncio
1056+ async def test_set_too_long_value (self ):
1057+ """Test that set_field with a too-long value raises the expected error"""
1058+ ctx = get_multiprocessing_context ()
1059+ parent_conn , child_conn = ctx .Pipe ()
1060+
1061+ device_name = create_random_prefix ()
1062+
1063+ process = ctx .Process (
1064+ target = self .get_set_too_long_value ,
1065+ args = (device_name , child_conn ),
1066+ )
1067+
1068+ process .start ()
1069+
1070+ log ("PARENT: Child started, waiting for R command" )
1071+
1072+ from aioca import camonitor
1073+
1074+ try :
1075+ # Wait for message that IOC has started
1076+ select_and_recv (parent_conn , "R" )
1077+
1078+ log ("PARENT: received R command" )
1079+
1080+ queue = asyncio .Queue ()
1081+ record = device_name + ":" + self .test_result_rec
1082+ monitor = camonitor (record , queue .put )
1083+
1084+ log (f"PARENT: monitoring { record } " )
1085+ new_val = await asyncio .wait_for (queue .get (), TIMEOUT )
1086+ log (f"PARENT: new_val is { new_val } " )
1087+ assert new_val == True , "Test failed, value was not True"
1088+
1089+ finally :
1090+ monitor .close ()
1091+ # Clear the cache before stopping the IOC stops
1092+ # "channel disconnected" error messages
1093+ aioca_cleanup ()
1094+
1095+ log ("PARENT: Sending Done command to child" )
1096+ parent_conn .send ("D" ) # "Done"
1097+ process .join (timeout = TIMEOUT )
1098+ log (f"PARENT: Join completed with exitcode { process .exitcode } " )
1099+ if process .exitcode is None :
1100+ pytest .fail ("Process did not terminate" )
0 commit comments