Parse a failures file to extract failed test details.
(file_path)
| 134 | |
| 135 | |
| 136 | def parse_failures_file(file_path): |
| 137 | """Parse a failures file to extract failed test details.""" |
| 138 | failures = [] |
| 139 | try: |
| 140 | with open(file_path, "r") as f: |
| 141 | content = f.read() |
| 142 | |
| 143 | # We don't need the base file name anymore as we're getting test paths from summary |
| 144 | |
| 145 | # Check if it's a short stack format |
| 146 | if "============================= FAILURES SHORT STACK =============================" in content: |
| 147 | # First, look for pytest-style failure headers with underscores and clean them up |
| 148 | test_headers = re.findall(r"_{5,}\s+([^_\n]+?)\s+_{5,}", content) |
| 149 | |
| 150 | for test_name in test_headers: |
| 151 | test_name = test_name.strip() |
| 152 | # Make sure it's a valid test name (contains a dot and doesn't look like a number) |
| 153 | if "." in test_name and not test_name.replace(".", "").isdigit(): |
| 154 | # For test names missing the full path, check if we can reconstruct it from failures_line.txt |
| 155 | # This is a best effort - we won't always have the line file available |
| 156 | if not test_name.endswith(".py") and "::" not in test_name and "/" not in test_name: |
| 157 | # Try to look for a corresponding line file |
| 158 | line_file = file_path.replace("_failures_short.txt", "_failures_line.txt") |
| 159 | if os.path.exists(line_file): |
| 160 | try: |
| 161 | with open(line_file, "r") as lf: |
| 162 | line_content = lf.read() |
| 163 | # Look for test name in line file which might have the full path |
| 164 | path_match = re.search( |
| 165 | r"(tests/[\w/]+\.py::[^:]+::" + test_name.split(".")[-1] + ")", |
| 166 | line_content, |
| 167 | ) |
| 168 | if path_match: |
| 169 | test_name = path_match.group(1) |
| 170 | except Exception: |
| 171 | pass # If we can't read the line file, just use what we have |
| 172 | |
| 173 | failures.append( |
| 174 | { |
| 175 | "test": test_name, |
| 176 | "error": "Error occurred", |
| 177 | "original_test_name": test_name, # Keep original for reference |
| 178 | } |
| 179 | ) |
| 180 | |
| 181 | # If we didn't find any pytest-style headers, try other formats |
| 182 | if not failures: |
| 183 | # Look for test names at the beginning of the file (in first few lines) |
| 184 | first_lines = content.split("\n")[:20] # Look at first 20 lines |
| 185 | for line in first_lines: |
| 186 | # Look for test names in various formats |
| 187 | # Format: tests/file.py::TestClass::test_method |
| 188 | path_match = re.search(r"(tests/[\w/]+\.py::[\w\.]+::\w+)", line) |
| 189 | # Format: TestClass.test_method |
| 190 | class_match = re.search(r"([A-Za-z][A-Za-z0-9_]+\.[A-Za-z][A-Za-z0-9_]+)", line) |
| 191 | |
| 192 | if path_match: |
| 193 | test_name = path_match.group(1) |
no test coverage detected
searching dependent graphs…