@@ -176,6 +176,38 @@ def _has_real_crash(dmesg_text, filters):
176176 return True
177177
178178
179+ def _run_one_test (test_dir , output_dir , target , prog ):
180+ """Run a single test and save stdout/stderr. Returns (retcode, elapsed)."""
181+ t1 = time .monotonic ()
182+ try :
183+ ret = subprocess .run (
184+ ['./run_kselftest.sh' , '-t' , f'{ target } :{ prog } ' ],
185+ cwd = test_dir ,
186+ capture_output = True ,
187+ timeout = 600 ,
188+ check = False
189+ )
190+ retcode = ret .returncode
191+ stdout = ret .stdout .decode ('utf-8' , 'ignore' )
192+ stderr = ret .stderr .decode ('utf-8' , 'ignore' )
193+ if not stdout and not stderr :
194+ print (f" { target } :{ prog } : no output (rc={ retcode } )" )
195+ except subprocess .TimeoutExpired :
196+ retcode = 1
197+ stdout = ''
198+ stderr = 'test timed out'
199+ print (f" { target } :{ prog } : timed out" )
200+ t2 = time .monotonic ()
201+ elapsed = round (t2 - t1 , 1 )
202+
203+ with open (os .path .join (output_dir , 'stdout' ), 'w' , encoding = 'utf-8' ) as fp :
204+ fp .write (stdout )
205+ with open (os .path .join (output_dir , 'stderr' ), 'w' , encoding = 'utf-8' ) as fp :
206+ fp .write (stderr )
207+
208+ return retcode , elapsed
209+
210+
179211def run_tests (test_dir , results_dir ):
180212 """Execute kselftest in 'installed' form.
181213
@@ -241,31 +273,12 @@ def run_tests(test_dir, results_dir):
241273 os .makedirs (test_results_dir , exist_ok = True )
242274
243275 # Run the test
244- t1 = time .monotonic ()
245- try :
246- ret = subprocess .run (
247- ['./run_kselftest.sh' , '-t' , f'{ target } :{ prog } ' ],
248- cwd = test_dir ,
249- capture_output = True ,
250- timeout = 600 ,
251- check = False
252- )
253- retcode = ret .returncode
254- stdout = ret .stdout .decode ('utf-8' , 'ignore' )
255- stderr = ret .stderr .decode ('utf-8' , 'ignore' )
256- if not stdout and not stderr :
257- print (f"[{ test_idx + 1 } /{ len (tests )} ] { test_name } : "
258- f"no output (rc={ retcode } )" )
259- except subprocess .TimeoutExpired :
260- retcode = 1
261- stdout = ''
262- stderr = 'test timed out'
263- print (f"[{ test_idx + 1 } /{ len (tests )} ] { test_name } : timed out" )
264- t2 = time .monotonic ()
265- elapsed = round (t2 - t1 , 1 )
276+ retcode , elapsed = _run_one_test (test_dir , test_results_dir ,
277+ target , prog )
266278
267279 # Drain dmesg produced during this test
268280 test_dmesg = dmesg .drain ()
281+ crash_fps = set ()
269282 if test_dmesg :
270283 with open (os .path .join (test_results_dir , 'dmesg' ), 'w' ,
271284 encoding = 'utf-8' ) as fp :
@@ -278,15 +291,41 @@ def run_tests(test_dir, results_dir):
278291 _lines , fps = extract_crash (test_dmesg , '' , lambda : filters )
279292 print (f"[{ test_idx + 1 } /{ len (tests )} ] { test_name } : "
280293 f"kernel crash in dmesg (ignored: { ', ' .join (fps )} )" )
281-
282- # Save output and metadata
283- with open (os .path .join (test_results_dir , 'stdout' ), 'w' , encoding = 'utf-8' ) as fp :
284- fp .write (stdout )
285- with open (os .path .join (test_results_dir , 'stderr' ), 'w' , encoding = 'utf-8' ) as fp :
286- fp .write (stderr )
294+ # Always extract fingerprints for the info file
295+ if has_crash (test_dmesg ):
296+ _lines , crash_fps = extract_crash (test_dmesg , '' , lambda : filters )
297+
298+ # Retry if the test failed and no crash
299+ retry_retcode = None
300+ if retcode not in (0 , 4 ) and not crashed :
301+ print (f"[{ test_idx + 1 } /{ len (tests )} ] Retrying { test_name } " )
302+ retry_dir = os .path .join (results_dir , f'{ dir_name } -retry' )
303+ os .makedirs (retry_dir , exist_ok = True )
304+ retry_retcode , _retry_elapsed = _run_one_test (
305+ test_dir , retry_dir , target , prog )
306+ # Drain retry dmesg
307+ retry_dmesg = dmesg .drain ()
308+ if retry_dmesg :
309+ with open (os .path .join (retry_dir , 'dmesg' ), 'w' ,
310+ encoding = 'utf-8' ) as fp :
311+ fp .write (retry_dmesg )
312+ if _has_real_crash (retry_dmesg , filters ):
313+ crashed = True
314+ if has_crash (retry_dmesg ):
315+ _lines , rfps = extract_crash (retry_dmesg , '' , lambda : filters )
316+ crash_fps .update (rfps )
317+ print (f"[{ test_idx + 1 } /{ len (tests )} ] { test_name } : "
318+ f"retry rc={ retry_retcode } " )
319+
320+ # Save metadata
321+ info = {'retcode' : retcode , 'time' : elapsed ,
322+ 'target' : target , 'prog' : prog }
323+ if retry_retcode is not None :
324+ info ['retry_retcode' ] = retry_retcode
325+ if crash_fps :
326+ info ['crashes' ] = list (crash_fps )
287327 with open (os .path .join (test_results_dir , 'info' ), 'w' , encoding = 'utf-8' ) as fp :
288- json .dump ({'retcode' : retcode , 'time' : elapsed ,
289- 'target' : target , 'prog' : prog }, fp )
328+ json .dump (info , fp )
290329
291330 print (f"[{ test_idx + 1 } /{ len (tests )} ] { test_name } : rc={ retcode } ({ elapsed } s)" )
292331
0 commit comments