Skip to content

Commit 262a041

Browse files
committed
contest: hw: retry failed tests and record crashes
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
1 parent 0b93f2c commit 262a041

2 files changed

Lines changed: 97 additions & 37 deletions

File tree

contest/hw/lib/deployer.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,17 @@ def fetch_results(machine_ips, reservation_id, results_path):
416416
check=False)
417417

418418

419+
def _retcode_to_result(retcode, stdout):
420+
"""Map a test return code + stdout to pass/fail/skip."""
421+
if retcode == 4:
422+
return 'skip'
423+
if retcode != 0:
424+
return 'fail'
425+
if 'ok' not in stdout.lower():
426+
return 'skip'
427+
return 'pass'
428+
429+
419430
def parse_results(results_path, link):
420431
"""Parse fetched test output into a vmksft-p-style result list.
421432
@@ -454,13 +465,19 @@ def parse_results(results_path, link):
454465
stdout = fp.read()
455466

456467
# Determine result
457-
result = 'pass'
458-
if retcode == 4:
459-
result = 'skip'
460-
elif retcode != 0:
461-
result = 'fail'
462-
if 'ok' not in stdout.lower() and result == 'pass':
463-
result = 'skip'
468+
result = _retcode_to_result(retcode, stdout)
469+
470+
# Determine retry result if present
471+
retry_result = None
472+
if 'retry_retcode' in info:
473+
retry_stdout = ''
474+
retry_dir = os.path.join(output_dir, entry + '-retry')
475+
retry_stdout_path = os.path.join(retry_dir, 'stdout')
476+
if os.path.exists(retry_stdout_path):
477+
with open(retry_stdout_path, encoding='utf-8') as fp:
478+
retry_stdout = fp.read()
479+
retry_result = _retcode_to_result(info['retry_retcode'],
480+
retry_stdout)
464481

465482
safe_name = re.sub(r'[^0-9a-zA-Z]+', '-', prog)
466483
if safe_name and safe_name[-1] == '-':
@@ -474,6 +491,10 @@ def parse_results(results_path, link):
474491
}
475492
if 'time' in info:
476493
outcome['time'] = info['time']
494+
if retry_result is not None:
495+
outcome['retry'] = retry_result
496+
if info.get('crashes'):
497+
outcome['crashes'] = info['crashes']
477498
cases.append(outcome)
478499

479500
# Check .attempted for crashed tests (attempted but no output)

contest/hw/lib/runner.py

Lines changed: 69 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
179211
def 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

Comments
 (0)