@@ -904,3 +904,203 @@ 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 , \
958+ f"EGU field was not { egu } , was { readback_egu } "
959+
960+ log ("CHILD: assert passed" )
961+
962+ # Test completed, report to listening camonitor
963+ bi .set (True )
964+
965+ # Keep process alive while main thread works.
966+ while (True ):
967+ if conn .poll (TIMEOUT ):
968+ val = conn .recv ()
969+ if val == "D" : # "Done"
970+ break
971+
972+ log ("CHILD: Received exit command, child exiting" )
973+
974+
975+ @pytest .mark .asyncio
976+ async def test_get_set (self ):
977+ """Test a simple set_field/get_field is successful"""
978+ ctx = get_multiprocessing_context ()
979+ parent_conn , child_conn = ctx .Pipe ()
980+
981+ device_name = create_random_prefix ()
982+
983+ process = ctx .Process (
984+ target = self .get_set_test_func ,
985+ args = (device_name , child_conn ),
986+ )
987+
988+ process .start ()
989+
990+ log ("PARENT: Child started, waiting for R command" )
991+
992+ from aioca import camonitor
993+
994+ try :
995+ # Wait for message that IOC has started
996+ select_and_recv (parent_conn , "R" )
997+
998+ log ("PARENT: received R command" )
999+
1000+ queue = asyncio .Queue ()
1001+ record = device_name + ":" + self .test_result_rec
1002+ monitor = camonitor (record , queue .put )
1003+
1004+ log (f"PARENT: monitoring { record } " )
1005+ new_val = await asyncio .wait_for (queue .get (), TIMEOUT )
1006+ log (f"PARENT: new_val is { new_val } " )
1007+ assert new_val == 1 , \
1008+ f"Test failed, value was not 1(True), was { new_val } "
1009+
1010+
1011+ finally :
1012+ monitor .close ()
1013+ # Clear the cache before stopping the IOC stops
1014+ # "channel disconnected" error messages
1015+ aioca_cleanup ()
1016+
1017+ log ("PARENT: Sending Done command to child" )
1018+ parent_conn .send ("D" ) # "Done"
1019+ process .join (timeout = TIMEOUT )
1020+ log (f"PARENT: Join completed with exitcode { process .exitcode } " )
1021+ if process .exitcode is None :
1022+ pytest .fail ("Process did not terminate" )
1023+
1024+ def get_set_too_long_value (self , device_name , conn ):
1025+ """Run an IOC and deliberately call set_field with a too-long value"""
1026+
1027+ builder .SetDeviceName (device_name )
1028+
1029+ lo = builder .longOut ("TestLongOut" , EGU = "unset" , DRVH = 12 )
1030+
1031+ # Record to indicate success/failure
1032+ bi = builder .boolIn (self .test_result_rec , ZNAM = "FAILED" , ONAM = "SUCCESS" )
1033+
1034+ dispatcher = asyncio_dispatcher .AsyncioDispatcher ()
1035+ builder .LoadDatabase ()
1036+ softioc .iocInit (dispatcher )
1037+
1038+ conn .send ("R" ) # "Ready"
1039+
1040+ log ("CHILD: Sent R over Connection to Parent" )
1041+
1042+ # Set a too-long value and confirm it reports an error
1043+ try :
1044+ lo .set_field ("EGU" , "ThisStringIsFarTooLongToFitIntoTheEguField" )
1045+ except ValueError as e :
1046+ # Expected error, report success to listening camonitor
1047+ assert "byte string too long" in e .args [0 ]
1048+ bi .set (True )
1049+
1050+ # Keep process alive while main thread works.
1051+ while (True ):
1052+ if conn .poll (TIMEOUT ):
1053+ val = conn .recv ()
1054+ if val == "D" : # "Done"
1055+ break
1056+
1057+ log ("CHILD: Received exit command, child exiting" )
1058+
1059+ @pytest .mark .asyncio
1060+ async def test_set_too_long_value (self ):
1061+ """Test that set_field with a too-long value raises the expected
1062+ error"""
1063+ ctx = get_multiprocessing_context ()
1064+ parent_conn , child_conn = ctx .Pipe ()
1065+
1066+ device_name = create_random_prefix ()
1067+
1068+ process = ctx .Process (
1069+ target = self .get_set_too_long_value ,
1070+ args = (device_name , child_conn ),
1071+ )
1072+
1073+ process .start ()
1074+
1075+ log ("PARENT: Child started, waiting for R command" )
1076+
1077+ from aioca import camonitor
1078+
1079+ try :
1080+ # Wait for message that IOC has started
1081+ select_and_recv (parent_conn , "R" )
1082+
1083+ log ("PARENT: received R command" )
1084+
1085+ queue = asyncio .Queue ()
1086+ record = device_name + ":" + self .test_result_rec
1087+ monitor = camonitor (record , queue .put )
1088+
1089+ log (f"PARENT: monitoring { record } " )
1090+ new_val = await asyncio .wait_for (queue .get (), TIMEOUT )
1091+ log (f"PARENT: new_val is { new_val } " )
1092+ assert new_val == 1 , \
1093+ f"Test failed, value was not 1(True), was { new_val } "
1094+
1095+ finally :
1096+ monitor .close ()
1097+ # Clear the cache before stopping the IOC stops
1098+ # "channel disconnected" error messages
1099+ aioca_cleanup ()
1100+
1101+ log ("PARENT: Sending Done command to child" )
1102+ parent_conn .send ("D" ) # "Done"
1103+ process .join (timeout = TIMEOUT )
1104+ log (f"PARENT: Join completed with exitcode { process .exitcode } " )
1105+ if process .exitcode is None :
1106+ pytest .fail ("Process did not terminate" )
0 commit comments