spiegel_podman/hack/ci/github_log_summary.py
Paul Holzinger 66c6da6e84
re-enable logformatter and post failure summary
Adapt the logformatter script to run again to produce nicely formatted
html logs. Because github cannot upload and show raw html files we can
only add it to the log archive which must be downloaded and viewed
locally.

To improve the online experience however github does have the
GITHUB_STEP_SUMMARY logic which allows us to produce markdown which will
be shown on the summary page for a given test run.

The problem is even though github markdown supports some html is does
not support CSS and out custom style of the logformatter, in addition
the output size is limited so I wrote another script parsing the html
output again and turning it into plain test for only the failed tests
and then show this as plain text inside codeblocks in the markdown.

With this we have a short failure summary which should display all
failures at once in the run page so maintainers can see if the failed
log was just some flake or an actual problem with the PR.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
2026-07-29 14:49:23 +02:00

128 lines
3.9 KiB
Python
Executable file

#!/usr/bin/env python
from html.parser import HTMLParser
import html
import sys
class GinkgoLogFilterParser(HTMLParser):
def __init__(self):
super().__init__()
# Stack to keep track of nested elements
self.stack = []
# Store the raw HTML strings of matching 'tt' elements
self.results = []
def _get_classes(self, attrs):
"""Helper to extract classes from an attribute list."""
for attr, value in attrs:
if attr == 'class' and value:
return value.split()
return []
def handle_starttag(self, tag, attrs):
classes = self._get_classes(attrs)
is_tt = 'tt' in classes
is_failed = 'log-failed' in classes
# If we see a 'log-failed' class, flag all 'tt' ancestors in the stack
if is_failed:
for node in self.stack:
if node['is_tt']:
node['keep'] = True
# Push the new element to the stack
self.stack.append({
'tag': tag,
'text_chunks': [],
'is_tt': is_tt,
'keep': False
})
def handle_startendtag(self, tag, attrs):
# Handle self-closing tags just to check for the failure class
classes = self._get_classes(attrs)
if 'log-failed' in classes:
for node in self.stack:
if node['is_tt']:
node['keep'] = True
def handle_data(self, data):
# Capture raw text data
if self.stack:
self.stack[-1]['text_chunks'].append(data)
def handle_endtag(self, tag):
# Find the matching start tag in the stack
for i in reversed(range(len(self.stack))):
if self.stack[i]['tag'] == tag:
# Pop the matching tag and any unclosed children
while len(self.stack) > i:
node = self.stack.pop()
# Join all the collected text for this node
node_text = "".join(node['text_chunks'])
# Trim down the spaces for the ginkgo long indentation.
node_text = node_text.removeprefix(' ' * 9)
# If this is a 'tt' element and it contains a 'log-failed' child, save the text
if node['is_tt'] and node['keep']:
self.results.append(node_text)
# Pass the text of this node up to its parent so we don't lose inner text
if self.stack:
self.stack[-1]['text_chunks'].append(node_text)
break
class BatsLogFilterParser(HTMLParser):
def __init__(self):
super().__init__()
# Current Tag which text should be stored
self.record = None
self.data = ""
def _get_classes(self, attrs):
"""Helper to extract classes from an attribute list."""
for attr, value in attrs:
if attr == 'class' and value:
return value.split()
return []
def handle_starttag(self, tag, attrs):
classes = self._get_classes(attrs)
if 'bats-failed' in classes or 'bats-log-failblock' in classes or 'bats-log' in classes:
self.record = tag
def handle_data(self, data):
if self.record:
self.data += data
def handle_endtag(self, tag):
if self.record == tag:
self.data += "\n"
self.record = None
def filter_html_file(file_path):
# Read the HTML content
with open(file_path, 'r', encoding='utf-8') as f:
html_content = f.read()
if 'int-' in file_path:
parser = GinkgoLogFilterParser()
parser.feed(html_content)
return parser.results
parser = BatsLogFilterParser()
parser.feed(html_content)
return [parser.data]
# Running the filter
matching_elements = filter_html_file(sys.argv[1])
for element in matching_elements:
print(f"```")
print(element)
print("```")