Parse meson testlog.txt into structured test entries. Args: testlog_path: Path to testlog.txt file Returns: List of TestLogEntry objects, one per test
(testlog_path: Path)
| 21 | |
| 22 | |
| 23 | def parse_testlog(testlog_path: Path) -> list[TestLogEntry]: |
| 24 | """ |
| 25 | Parse meson testlog.txt into structured test entries. |
| 26 | |
| 27 | Args: |
| 28 | testlog_path: Path to testlog.txt file |
| 29 | |
| 30 | Returns: |
| 31 | List of TestLogEntry objects, one per test |
| 32 | """ |
| 33 | if not testlog_path.exists(): |
| 34 | return [] |
| 35 | |
| 36 | with open(testlog_path, "r", encoding="utf-8", errors="replace") as f: |
| 37 | content = f.read() |
| 38 | |
| 39 | # Split into test sections using the separator pattern |
| 40 | # Pattern matches: "=================================== N/M ===================================" |
| 41 | section_pattern = r"={30,}\s+\d+/\d+\s+={30,}" |
| 42 | sections = re.split(section_pattern, content) |
| 43 | |
| 44 | entries: list[TestLogEntry] = [] |
| 45 | |
| 46 | for section in sections: |
| 47 | if not section.strip(): |
| 48 | continue |
| 49 | |
| 50 | # Extract test name |
| 51 | # Meson testlog uses two formats: |
| 52 | # Unit tests: "test: fastled:test_name" |
| 53 | # Example tests: "test: examples - fastled:example-Name" |
| 54 | # We need the qualified name (e.g., "fastled:example-Name") |
| 55 | test_match = re.search(r"test:\s+(?:\S+\s+-\s+)?(\S+:\S+)", section) |
| 56 | if not test_match: |
| 57 | # Fallback: capture first word (for non-suite tests) |
| 58 | test_match = re.search(r"test:\s+(\S+)", section) |
| 59 | if not test_match: |
| 60 | continue |
| 61 | |
| 62 | test_name = test_match.group(1) |
| 63 | |
| 64 | # Extract result |
| 65 | result_match = re.search(r"result:\s+(.+)", section) |
| 66 | result = result_match.group(1) if result_match else "unknown" |
| 67 | |
| 68 | # Extract duration |
| 69 | duration_match = re.search(r"duration:\s+([\d.]+s)", section) |
| 70 | duration = duration_match.group(1) if duration_match else "N/A" |
| 71 | |
| 72 | # Extract stdout section (between "--- stdout ---" and "--- stderr ---" or next section) |
| 73 | stdout_match = re.search( |
| 74 | r"-+ stdout -+\n(.*?)(?:-+ stderr -+|$)", section, re.DOTALL |
| 75 | ) |
| 76 | stdout = stdout_match.group(1).strip() if stdout_match else "" |
| 77 | |
| 78 | # Extract stderr section (between "--- stderr ---" and next section or end) |
| 79 | # Note: Use negative lookahead to avoid matching box-drawing characters (═) |
| 80 | # Only match the actual section separator: ={30,} at start of line |
no test coverage detected