#!/usr/bin/env python3 import os import json from typing import Dict, List from dataclasses import dataclass from datetime import datetime import subprocess import re import requests CGAL_SERVER_URL = "https://cgal.geometryfactory.com/CGAL" LATEST_VERSION_URL = f"{CGAL_SERVER_URL}/Releases/LATEST" JSON_DATA_URL_TEMPLATE = f"{ CGAL_SERVER_URL}/testsuite/CGAL-{{version}}/search_index.json" TESTSUITE_URL_TEMPLATE = f"{ CGAL_SERVER_URL}/testsuite/results-{{version}}.shtml" TIMEOUT_DURATION = 10 @dataclass class TPLInfo: name: str version: str status: str @dataclass class PlatformInfo: name: str debug: str os: str tester: str compiler: str tpl_info: List[TPLInfo] def fetch_data_from_url(url: str) -> str: """Fetch data from a given URL.""" response = requests.get(url, timeout=TIMEOUT_DURATION) response.raise_for_status() return response.text.strip() def get_latest_version() -> str: """Return latest CGAL version from LATEST (CGAL-.tar.gz)""" tarball_name = fetch_data_from_url(LATEST_VERSION_URL) match = re.match(r'CGAL-([^.]+\.[^-]+-[^-]+-\d+)', tarball_name) if not match: raise ValueError(f"Unexpected tarball name format: {tarball_name}") return match.group(1) def fetch_json_data(version: str) -> Dict: """Fetch JSON data for the given CGAL testsuite.""" url = JSON_DATA_URL_TEMPLATE.format(version=version) json_data = fetch_data_from_url(url) return json.loads(json_data) def analyze_tpl_data(json_data: Dict) -> List[PlatformInfo]: """Analyze TPL data from JSON and return a list of PlatformInfo.""" platforms_info = [] for platform in json_data.get('platforms', []): tpl_list = [ TPLInfo( name=item.get('name', 'Unknown'), version=item.get('version', 'N/A'), status=item.get('status', 'unknown') ) for item in platform.get('third_party_libs', []) ] platform_info = PlatformInfo( name=platform.get('platform_name', 'Unknown Platform'), debug=platform.get('debug', '-'), os=platform.get('operating_system', '-'), tester=platform.get('tester_name', '-'), compiler=platform.get('compiler', '-'), tpl_info=tpl_list ) platforms_info.append(platform_info) return platforms_info def get_docker_images() -> Dict[str, List[str]]: """ Get Docker image information by calling `list_test_runner_machines`. Returns a dictionary with machine names as keys and lists of images as values. """ try: script_dir = os.path.dirname(os.path.abspath(__file__)) result = subprocess.run( [os.path.join(script_dir, 'list_test_runner_machines'), '--plain'], capture_output=True, text=True, check=True ) output = result.stdout.strip() machines_info = {} current_machine = None parsing_images = False for line in output.splitlines(): if line.startswith("## "): current_machine = line.strip("# ").strip() machines_info[current_machine] = [] parsing_images = False elif line.startswith("Tested images:"): parsing_images = True elif parsing_images and (line.startswith("cgal/testsuite-docker:") or line.startswith("docker.io/cgal/testsuite-docker:")): machines_info[current_machine].append(line.strip()) return machines_info except subprocess.CalledProcessError as e: raise RuntimeError( f"Error running `list_test_runner_machines`: {e}") from e except Exception as e: raise RuntimeError(f"Error parsing Docker information: {e}") from e def add_docker_summary(report: List[str], machines_info: Dict[str, List[str]]): """Add a summary of Docker images used on each machine to the report.""" report.append("\n## Docker Test Summary") for machine, images in machines_info.items(): report.append(f"\n### Machine: {machine} ({len(images)} images)") report.append("\n#### Tested Images\n") for image in images: report.append(f"- {image}") def generate_markdown_report(platforms_info: List[PlatformInfo], version: str) -> str: """Generate a markdown report from the platforms information.""" machines_info = get_docker_images() report = [] report.append("# TestSuite Report") report.append(f"\nGenerated on: { datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") url = TESTSUITE_URL_TEMPLATE.format(version=version) report.append(f"\nCGAL Version: [{version}]({url})") add_docker_summary(report, machines_info) report.append("\n## Platforms Summary\n") report.append("| Platform | Debug | OS | Tester | Compiler |") report.append("|----------|-------|----|--------|----------|") for platform in platforms_info: report.append( f"| {platform.name} | {platform.debug} | {platform.os} | " f"{platform.tester} | {platform.compiler} |" ) report.append("\n## Detailed Third-party Libraries") for platform in platforms_info: report.append(f"\n### Platform: {platform.name}\n") tpl_list = sorted(platform.tpl_info, key=lambda x: x.name) report.append("| Library Name | Version | Status |") report.append("|--------------|---------|--------|") for tpl in tpl_list: version_str = str(tpl.version) if tpl.version else "N/A" status_str = "❌" if tpl.version == "not found" else "✅" report.append(f"| {tpl.name} | {version_str} | {status_str} |") found_tpls = sum(1 for tpl in tpl_list if tpl.version != "not found") total_tpls = len(tpl_list) report.append( f"\n**Summary**: found {found_tpls} third-party libraries out of {total_tpls}") return "\n".join(report) def main(): """Main function to generate the testsuite report.""" try: version = get_latest_version() json_data = fetch_json_data(version) platforms_info = analyze_tpl_data(json_data) markdown_report = generate_markdown_report(platforms_info, version) print(markdown_report) except requests.RequestException as e: print(f"**Error fetching data:**\n\n```\n{str(e)}\n```\n") raise except json.JSONDecodeError as e: print(f"**Error: Invalid JSON data**\n\n```\n{str(e)}\n```") print(f"\nFile:\n\n```json\n{e.doc}\n```") raise except Exception as e: print(f"**Error processing data:**\n\n```\n{str(e)}\n```\n") raise if __name__ == "__main__": main()