mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-08-13 20:29:35 +00:00
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>
128 lines
3.9 KiB
Python
Executable file
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("```")
|