@@ -41,6 +41,8 @@ class Harness:
4141 RUN_FAILED = "PROJECT EXECUTION FAILED"
4242 run_id_pattern = r"RunID: (?P<run_id>.*)"
4343
44+ cacheable = False
45+
4446 def __init__ (self ):
4547 self ._status = TwisterStatus .NONE
4648 self .reason = None
@@ -967,37 +969,62 @@ class Bsim(Harness):
967969 DEFAULT_VERBOSITY = 2
968970 DEFAULT_SIM_LENGTH = 60e6
969971
972+ BSIM_READY_TIMEOUT_S = 20
973+
974+ cacheable = True
975+
970976 def __init__ (self ):
971977 super ().__init__ ()
972978 self ._bsim_out_path = os .getenv ('BSIM_OUT_PATH' , '' )
973- self ._exe_path = None
979+ self ._exe_paths = []
974980 self ._tc_output = []
981+ self ._start_time = 0
975982
976- @property
977- def exe_path (self ):
978- if self ._exe_path :
979- return self ._exe_path
983+ def _set_start_time (self ):
984+ self ._start_time = time .time ()
985+
986+ def _get_exe_path (self , index ):
987+ return self ._exe_paths [index if index < len (self ._exe_paths ) else 0 ]
988+
989+ def configure (self , instance ):
990+ super ().configure (instance )
980991
981992 if not self ._bsim_out_path :
982- logger .warning ('Cannot copy bsim exe - BSIM_OUT_PATH not provided.' )
983- return self ._exe_path
993+ raise Exception ('Cannot copy bsim exe - BSIM_OUT_PATH not provided.' )
984994
985- new_exe_name : str = self .instance .testsuite .harness_config .get ('bsim_exe_name' , '' )
986- if new_exe_name :
987- new_exe_name = f'bs_{ self .instance .platform .name } _{ new_exe_name } '
988- else :
989- new_exe_name = f'bs_{ self .instance .name } '
995+ exe_names = []
996+ for exe_name in self .instance .testsuite .harness_config .get ('bsim_exe_name' , []):
997+ new_exe_name = f'bs_{ self .instance .platform .name } _{ exe_name } '
998+ exe_names .append (
999+ new_exe_name .replace (os .path .sep , '_' ).replace ('.' , '_' ).replace ('@' , '_' ))
1000+
1001+ if not exe_names :
1002+ exe_names = [f'bs_{ self .instance .name } ' ]
9901003
991- new_exe_name = new_exe_name .replace (os .path .sep , '_' ).replace ('.' , '_' ).replace ('@' , '_' )
992- self ._exe_path = os .path .join (self ._bsim_out_path , 'bin' , new_exe_name )
993- return self ._exe_path
1004+ self ._exe_paths = \
1005+ [os .path .join (self ._bsim_out_path , 'bin' , exe_name ) for exe_name in exe_names ]
1006+
1007+ def clean_exes (self ):
1008+ for exe_path in [self ._get_exe_path (i ) for i in range (len (self ._exe_paths ))]:
1009+ if os .path .exists (exe_path ):
1010+ os .remove (exe_path )
1011+
1012+ self ._set_start_time ()
1013+
1014+ def wait_bsim_ready (self ):
1015+ start_time = time .time ()
1016+ while time .time () - start_time < Bsim .BSIM_READY_TIMEOUT_S :
1017+ if all ([os .path .exists (self ._get_exe_path (i )) for i in range (len (self ._exe_paths ))]):
1018+ return True
1019+ time .sleep (0.1 )
1020+
1021+ return False
9941022
9951023 def build (self ):
9961024 """
9971025 Copying the application executable to BabbleSim's bin directory enables
9981026 running multidevice bsim tests after twister has built them.
9991027 """
1000-
10011028 if self .instance is None :
10021029 return
10031030
@@ -1006,8 +1033,16 @@ def build(self):
10061033 logger .warning ('Cannot copy bsim exe - cannot find original executable.' )
10071034 return
10081035
1009- logger .debug (f'Copying executable from { original_exe_path } to { self .exe_path } ' )
1010- shutil .copy (original_exe_path , self .exe_path )
1036+ try :
1037+ new_exe_path = self ._get_exe_path (0 )
1038+ logger .debug (f'Copying executable from { original_exe_path } to { new_exe_path } ' )
1039+ shutil .copy (original_exe_path , new_exe_path )
1040+ self .status = TwisterStatus .PASS
1041+ except Exception as e :
1042+ logger .error (f'Failed to copy bsim exe: { e } ' )
1043+ self .status = TwisterStatus .ERROR
1044+ finally :
1045+ self .instance .execution_time = time .time () - self ._start_time
10111046
10121047 def _run_cmd (self , cmd , timeout ):
10131048 with subprocess .Popen (cmd , stdout = subprocess .PIPE , stderr = subprocess .PIPE ,
@@ -1020,18 +1055,14 @@ def _run_cmd(self, cmd, timeout):
10201055 terminate_process (proc )
10211056 logger .warning ('Timeout has occurred. Can be extended in testspec file. '
10221057 f'Currently set to { timeout } seconds.' )
1023- self .status = TwisterStatus .FAIL
1058+ self .status = TwisterStatus .ERROR
10241059 proc .wait (timeout )
10251060 except subprocess .TimeoutExpired :
1026- self .status = TwisterStatus .FAIL
1061+ self .status = TwisterStatus .ERROR
10271062 proc .kill ()
10281063
10291064 if self .status == TwisterStatus .NONE :
10301065 self .status = TwisterStatus .FAIL if proc .returncode != 0 else TwisterStatus .PASS
1031- else :
1032- self .status = TwisterStatus .FAIL \
1033- if proc .returncode != 0 or self .status in [TwisterStatus .ERROR , TwisterStatus .FAIL ] \
1034- else TwisterStatus .PASS
10351066
10361067 def _output_reader (self , proc ):
10371068 while proc .stdout .readable () and proc .poll () is None :
@@ -1054,40 +1085,42 @@ def _generate_commands(self):
10541085 if not test_ids :
10551086 logger .error ('No test ids specified for bsim test' )
10561087 self .status = TwisterStatus .ERROR
1057- return
1088+ return []
10581089
1059- cmds = [[self .exe_path , verbosity , suite_id , f'-d={ i } ' , f'-testid={ t_id } ' ] + extra_args
1090+ cmds = [[self ._get_exe_path ( i ) , verbosity , suite_id , f'-d={ i } ' , f'-testid={ t_id } ' ] + extra_args
10601091 for i , t_id in enumerate (test_ids )]
10611092 cmds .append ([bsim_phy_path , verbosity , suite_id , f'-D={ len (test_ids )} ' , sim_length ])
10621093
10631094 return cmds
10641095
10651096 def bsim_run (self , timeout ):
10661097 try :
1098+ self ._set_start_time ()
1099+
10671100 threads = []
1068- start_time = time .time ()
10691101 for cmd in self ._generate_commands ():
10701102 t = threading .Thread (target = lambda c = cmd : self ._run_cmd (c , timeout ))
10711103 threads .append (t )
10721104 t .start ()
10731105
10741106 for t in threads :
10751107 t .join (timeout = timeout )
1076-
1077- self .instance .execution_time = time .time () - start_time
1108+ except Exception as e :
1109+ logger .error (f'BSIM test failed: { e } ' )
1110+ self .status = TwisterStatus .ERROR
10781111 finally :
10791112 self ._update_test_status ()
10801113
10811114 def _update_test_status (self ):
1115+ self .instance .execution_time += time .time () - self ._start_time
10821116 if not self .instance .testcases :
10831117 self .instance .init_cases ()
10841118
1085- # currently there is alays one testcase per bsim suite
1119+ # currently there is always one testcase per bsim suite
10861120 self .instance .testcases [0 ].status = self .status if self .status != TwisterStatus .NONE else \
10871121 TwisterStatus .FAIL
10881122 self .instance .status = self .instance .testcases [0 ].status
10891123 if self .instance .status in [TwisterStatus .ERROR , TwisterStatus .FAIL ]:
1090- logger .warning (f'BSIM test failed: { self .instance .reason } ' )
10911124 self .instance .reason = self .instance .reason or 'Bsim test failed'
10921125 self .instance .testcases [0 ].output = '\n ' .join (self ._tc_output )
10931126
@@ -1231,15 +1264,41 @@ def _parse_report_file(self, report):
12311264
12321265class HarnessImporter :
12331266
1267+ cache = {}
1268+ cache_lock = threading .Lock ()
1269+
12341270 @staticmethod
1235- def get_harness (harness_name ):
1271+ def get_harness (instance : TestInstance ):
1272+ harness_class = HarnessImporter ._get_harness_class (instance .testsuite .harness )
1273+ if not harness_class :
1274+ return None
1275+
1276+ harness = None
1277+ with HarnessImporter .cache_lock :
1278+ if harness_class .cacheable and instance .name in HarnessImporter .cache :
1279+ harness = HarnessImporter .cache [instance .name ]
1280+ else :
1281+ harness = harness_class ()
1282+ if harness_class .cacheable :
1283+ harness .configure (instance )
1284+ HarnessImporter .cache [instance .name ] = harness
1285+
1286+ return harness
1287+
1288+ @staticmethod
1289+ def _get_harness_class (harness_name : str ):
12361290 thismodule = sys .modules [__name__ ]
12371291 try :
12381292 if harness_name :
1239- harness_class = getattr (thismodule , harness_name )
1293+ harness_class = getattr (thismodule , harness_name . capitalize () )
12401294 else :
12411295 harness_class = thismodule .Test
1242- return harness_class ()
1296+ return harness_class
12431297 except AttributeError as e :
12441298 logger .debug (f"harness { harness_name } not implemented: { e } " )
12451299 return None
1300+
1301+ @staticmethod
1302+ def get_harness_by_name (harness_name : str ):
1303+ harness_class = HarnessImporter ._get_harness_class (harness_name )
1304+ return harness_class () if harness_class else None
0 commit comments