#!/usr/bin/env python3 """ Credits to Arthur Vasseur for this script. https://github.com/ArthurVasseur/Vkd/blob/main/scripts/cts_report.py """ import sys import re import os import xml.etree.ElementTree as ET import pandas as pd from datetime import datetime from collections import Counter def parse_raw_log(log_text: str): """Extract XML blocks from a raw CTS log file.""" pattern = re.compile( r']*>.*?', re.DOTALL ) matches = pattern.findall(log_text) return matches def parse_xml_file(path: str): """Extract nodes from a pure XML file.""" tree = ET.parse(path) root = tree.getroot() return [ ET.tostring(elem, encoding="unicode") for elem in root.findall(".//TestCaseResult") ] def process_testcases(xml_blocks): """Convert XML test blocks into structured rows.""" rows = [] for block in xml_blocks: elem = ET.fromstring(block) case = elem.attrib.get("CasePath", "unknown") duration = elem.findtext("Number", default="0") result = elem.find("Result").attrib.get("StatusCode", "UNKNOWN") message = elem.findtext("Text", default="") rows.append({ "Test Case": case, "Duration (µs)": int(duration), "Status": result, "Message": message, "RawMessage": message, }) return rows def format_message_html(message: str) -> str: """Format test message for HTML display with proper handling of newlines and tabs.""" if not message: return "" import html import json import textwrap try: formatted = bytes(message, 'utf-8').decode('unicode_escape') except Exception: formatted = message for old, new in [('\\n', '\n'), ('\\t', '\t'), ('\\r', '\r')]: formatted = formatted.replace(old, new) formatted = textwrap.dedent(formatted).strip() try: if formatted.strip().startswith('{') or formatted.strip().startswith('['): parsed = json.loads(formatted) formatted = json.dumps(parsed, indent=2) escaped = html.escape(formatted) return f'
View JSON
{escaped}
' except (json.JSONDecodeError, ValueError): pass if '\n' in formatted or '\t' in formatted or len(message) > 100: escaped = html.escape(formatted) return f'
View details
{escaped}
' else: return html.escape(formatted) def status_to_html(status: str) -> str: cls = { "Pass": "status-Pass", "Fail": "status-Fail", "NotSupported": "status-NotSupported", }.get(status, "") return f'{status}' def calculate_statistics(df): """Calculate test statistics from the dataframe.""" status_counts = Counter(df['Status']) total_tests = len(df) total_duration = df['Duration (µs)'].sum() avg_duration = df['Duration (µs)'].mean() if total_tests > 0 else 0 pass_count = status_counts.get('Pass', 0) fail_count = status_counts.get('Fail', 0) not_supported_count = status_counts.get('NotSupported', 0) other_count = total_tests - (pass_count + fail_count + not_supported_count) pass_rate = (pass_count / total_tests * 100) if total_tests > 0 else 0 return { 'total': total_tests, 'pass': pass_count, 'fail': fail_count, 'not_supported': not_supported_count, 'other': other_count, 'pass_rate': pass_rate, 'total_duration_us': total_duration, 'total_duration_ms': total_duration / 1000, 'total_duration_s': total_duration / 1_000_000, 'avg_duration_us': avg_duration, } def generate_pie_chart_svg(stats): """Generate a simple SVG pie chart for test results.""" total = stats['total'] if total == 0: return "" pass_pct = stats['pass'] / total fail_pct = stats['fail'] / total not_supported_pct = stats['not_supported'] / total other_pct = stats['other'] / total segments = [] cumulative = 0 colors = { 'pass': '#22c55e', 'fail': '#f97373', 'not_supported': '#eab308', 'other': '#64748b' } for name, pct, color in [ ('Pass', pass_pct, colors['pass']), ('Fail', fail_pct, colors['fail']), ('Not Supported', not_supported_pct, colors['not_supported']), ('Other', other_pct, colors['other']) ]: if pct > 0: segments.append({ 'name': name, 'percentage': pct * 100, 'start': cumulative, 'end': cumulative + pct, 'color': color }) cumulative += pct svg_paths = [] radius = 80 cx, cy = 100, 100 for seg in segments: start_angle = seg['start'] * 2 * 3.14159 end_angle = seg['end'] * 2 * 3.14159 x1 = cx + radius * cos_approx(start_angle) y1 = cy + radius * sin_approx(start_angle) x2 = cx + radius * cos_approx(end_angle) y2 = cy + radius * sin_approx(end_angle) large_arc = 1 if (end_angle - start_angle) > 3.14159 else 0 path = f'M {cx} {cy} L {x1} {y1} A {radius} {radius} 0 {large_arc} 1 {x2} {y2} Z' svg_paths.append(f'') return f''' {chr(10).join(svg_paths)} ''' def cos_approx(angle): import math return math.cos(angle) def sin_approx(angle): import math return math.sin(angle) def main(): if len(sys.argv) != 3: print("Usage: cts_report.py ") sys.exit(1) input_path = sys.argv[1] output_path = sys.argv[2] if not os.path.exists(input_path): print(f"Error: input file not found: {input_path}") sys.exit(1) # Detect input format with open(input_path, "r", encoding="utf-8", errors="ignore") as f: content = f.read() if " 1: duration_str = f"{stats['total_duration_s']:.2f}s" else: duration_str = f"{stats['total_duration_ms']:.2f}ms" table_html = df.to_html( index=False, escape=False, justify="center", border=0, classes="cts-table", table_id="results-table" ) # Replace placeholders with actual formatted messages for i, msg in enumerate(formatted_messages): table_html = table_html.replace(f"__MSG_PLACEHOLDER_{i}__", msg) html = f""" Vulkan CTS Report

Vulkan CTS Report

Summary of test cases, status and timings

{generation_time}
Total: {stats['total']} tests
Passed
{stats['pass']}
{stats['pass_rate']:.1f}% success rate
Failed
{stats['fail']}
{(stats['fail'] / stats['total'] * 100) if stats['total'] > 0 else 0:.1f}% of total
Not Supported
{stats['not_supported']}
{(stats['not_supported'] / stats['total'] * 100) if stats['total'] > 0 else 0:.1f}% of total
Duration
{duration_str}
Avg: {stats['avg_duration_us']:.0f} µs/test
{pie_chart_svg}
{table_html}
""" with open(output_path, "w", encoding="utf-8") as f: f.write(html) print(f"[OK] HTML report saved to: {output_path}") print(f"\n--- Test Statistics ---") print(f"Total tests: {stats['total']}") print(f"Passed: {stats['pass']} ({stats['pass_rate']:.1f}%)") print(f"Failed: {stats['fail']} ({(stats['fail'] / stats['total'] * 100) if stats['total'] > 0 else 0:.1f}%)") print(f"Not Supported: {stats['not_supported']} ({(stats['not_supported'] / stats['total'] * 100) if stats['total'] > 0 else 0:.1f}%)") if stats['other'] > 0: print(f"Other: {stats['other']} ({(stats['other'] / stats['total'] * 100) if stats['total'] > 0 else 0:.1f}%)") print(f"Total Duration: {duration_str}") print(f"Average Duration: {stats['avg_duration_us']:.0f} µs/test") if __name__ == "__main__": main()