from datetime import datetime import platform import sys from typing import Optional from loguru import logger import pandas as pd from turing.config import REPORTS_DIR class TestReportGenerator: """ Handles the generation of structured Markdown reports specifically for test execution results. """ def __init__(self, context_name: str, report_category: str): self.context_name = context_name self.report_category = report_category self.timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") self.content = [] self.output_dir = REPORTS_DIR / self.report_category def add_header(self, text: str, level: int = 1): self.content.append(f"\n{'#' * level} {text}\n") def add_divider(self, style: str = "thin"): """Add a visual divider line.""" dividers = { "thin": "---", "thick": "___", "section": "\n---\n", } self.content.append(f"\n{dividers.get(style, dividers['thin'])}\n") def add_code_block(self, content: str, language: str = ""): """Add a code block.""" self.content.append(f"\n```{language}\n{content}\n```\n") def add_alert_box(self, message: str, box_type: str = "info"): """Add a styled alert box using blockquotes.""" box_headers = { "info": "INFO", "success": "SUCCESS", "warning": "WARNING", "error": "ERROR", } header = box_headers.get(box_type, "INFO") self.content.append(f"\n> **{header}**: {message}\n") def add_progress_bar(self, passed: int, total: int, width: int = 50): """Add an ASCII progress bar.""" if total == 0: percentage = 0 filled = 0 else: percentage = (passed / total * 100) filled = int(width * passed / total) empty = width - filled bar = "█" * filled + "░" * empty self.add_code_block(f"Progress: [{bar}] {percentage:.1f}%\nPassed: {passed}/{total} tests", "") def add_summary_box(self, total: int, passed: int, failed: int, skipped: int = 0): """Add a visually enhanced summary box.""" success_rate = (passed / total * 100) if total > 0 else 0 # Determine status if success_rate == 100: status = "ALL TESTS PASSED" elif success_rate >= 80: status = "MOSTLY PASSED" elif success_rate >= 50: status = "PARTIAL SUCCESS" else: status = "NEEDS ATTENTION" self.add_header("Executive Summary", level=2) self.add_text(f"**Overall Status:** {status}") self.add_text(f"**Success Rate:** {success_rate:.1f}%") # Summary table summary_data = [ ["Total Tests", str(total)], ["Passed", str(passed)], ["Failed", str(failed)], ] if skipped > 0: summary_data.append(["Skipped", str(skipped)]) summary_data.append(["Success Rate", f"{success_rate:.1f}%"]) df = pd.DataFrame(summary_data, columns=["Metric", "Count"]) self.add_dataframe(df, title=None, align=("left", "right")) # Progress bar self.add_text("**Visual Progress:**") self.add_progress_bar(passed, total) def add_environment_metadata(self): """Add enhanced environment metadata.""" self.add_header("Environment Information", level=2) metadata = [ ["Timestamp", datetime.now().strftime("%Y-%m-%d %H:%M:%S")], ["Context", self.context_name.upper()], ["Python Version", sys.version.split()[0]], ["Platform", platform.platform()], ["Architecture", platform.machine()], ] df = pd.DataFrame(metadata, columns=["Parameter", "Value"]) self.add_dataframe(df, title=None, align=("left", "left")) def add_text(self, text: str): self.content.append(f"\n{text}\n") def add_category_stats(self, df: pd.DataFrame, category: str): """Add statistics for a test category.""" total = len(df) passed = len(df[df['Result'] == "PASS"]) failed = len(df[df['Result'] == "FAIL"]) skipped = len(df[df['Result'] == "SKIP"]) stats = [ ["Total", str(total)], ["Passed", f"{passed} ({passed/total*100:.1f}%)" if total > 0 else "0"], ["Failed", f"{failed} ({failed/total*100:.1f}%)" if total > 0 else "0"], ] if skipped > 0: stats.append(["Skipped", f"{skipped} ({skipped/total*100:.1f}%)"]) stats_df = pd.DataFrame(stats, columns=["Status", "Count"]) self.add_dataframe(stats_df, title="Statistics", align=("left", "right")) def add_dataframe(self, df: pd.DataFrame, title: Optional[str] = None, align: tuple = None): """Add a formatted dataframe table.""" if title: self.add_header(title, level=3) if df.empty: self.content.append("\n_No data available._\n") return try: if not align: align = tuple(["left"] * len(df.columns)) table_md = df.to_markdown(index=False, tablefmt="pipe", colalign=align) self.content.append(f"\n{table_md}\n") except Exception as e: logger.warning(f"Tabulate error: {e}. Using simple text.") self.content.append(f"\n```text\n{df.to_string(index=False)}\n```\n") def save(self, filename: str = "test_report.md") -> str: """Save the report to a file.""" try: self.output_dir.mkdir(parents=True, exist_ok=True) file_path = self.output_dir / filename # Add footer self.add_divider("section") self.add_text(f"*Report generated on {datetime.now().strftime('%Y-%m-%d at %H:%M:%S')}*") self.add_text("*Powered by Turing Test Suite*") with open(file_path, "w", encoding="utf-8") as f: f.write("\n".join(self.content)) logger.info(f"Test report saved: {file_path}") return str(file_path) except Exception as e: logger.error(f"Save failed: {e}") raise