|
13 | 13 | import signal |
14 | 14 | import subprocess |
15 | 15 | import time |
| 16 | +from functools import lru_cache |
16 | 17 | from collections import OrderedDict, defaultdict |
17 | 18 | from itertools import chain |
18 | 19 | from statistics import median |
@@ -94,6 +95,31 @@ def filter_existing_tests(tests_to_run, repo_path): |
94 | 95 | return result |
95 | 96 |
|
96 | 97 |
|
| 98 | +@lru_cache(maxsize=None) |
| 99 | +def extract_fail_logs(log_path: str) -> dict[str, str]: |
| 100 | + with open(log_path, "r", encoding="utf-8") as log_file: |
| 101 | + text = log_file.read() |
| 102 | + |
| 103 | + # Regex matches: |
| 104 | + # - a line like "_____ test_something [param] _____" |
| 105 | + # - captures the test name (with params) |
| 106 | + # - captures everything up to the next header or end of file |
| 107 | + pattern = re.compile( |
| 108 | + r"_{5,}\s+([^\s].*?)\s+_{5,}(.*?)(?=_{5,}\s+[^\s].*?\s+_{5,}|\Z)", |
| 109 | + re.S, |
| 110 | + ) |
| 111 | + |
| 112 | + results = {} |
| 113 | + for match in pattern.finditer(text): |
| 114 | + test_name, body = match.groups() |
| 115 | + |
| 116 | + # Keep only sections that include a failure or captured log |
| 117 | + if "Captured log" in body or "FAILED" in body: |
| 118 | + results[test_name.strip()] = body.strip() |
| 119 | + |
| 120 | + return results |
| 121 | + |
| 122 | + |
97 | 123 | def _get_deselect_option(tests): |
98 | 124 | return " ".join([f"--deselect {t}" for t in tests]) |
99 | 125 |
|
@@ -438,8 +464,8 @@ def _handle_broken_tests( |
438 | 464 | def get_log_paths(test_name): |
439 | 465 | """Could be a list of logs for all tests or a dict with test name as a key""" |
440 | 466 | if isinstance(log_paths, dict): |
441 | | - return log_paths[test_name] |
442 | | - return log_paths |
| 467 | + return sorted(log_paths[test_name], reverse=True) |
| 468 | + return sorted(log_paths, reverse=True) |
443 | 469 |
|
444 | 470 | broken_tests_log = os.path.join(self.result_path, "broken_tests_handler.log") |
445 | 471 |
|
@@ -478,11 +504,13 @@ def get_log_paths(test_name): |
478 | 504 | for log_path in get_log_paths(failed_test): |
479 | 505 | if log_path.endswith(".log"): |
480 | 506 | log_file.write(f"Checking log file: {log_path}\n") |
481 | | - with open(log_path) as test_log: |
482 | | - if fail_message in test_log.read(): |
483 | | - log_file.write("Found fail message in logs\n") |
484 | | - mark_as_broken = True |
485 | | - break |
| 507 | + fail_logs = extract_fail_logs(log_path).get( |
| 508 | + failed_test.split("::")[-1] |
| 509 | + ) |
| 510 | + if fail_logs and fail_message in fail_logs: |
| 511 | + log_file.write("Found fail message in logs\n") |
| 512 | + mark_as_broken = True |
| 513 | + break |
486 | 514 |
|
487 | 515 | if mark_as_broken: |
488 | 516 | log_file.write(f"Moving test to BROKEN state\n") |
|
0 commit comments