#!/usr/bin/env python3 """ Test Report Generator for Phase 208 Integration & Performance Testing Generates comprehensive test reports combining integration, contract, performance, and quality test results. Supports JSON, HTML, and Markdown output formats. Usage: python generate_test_report.py --format markdown --output TEST_REPORT.md python generate_test_report.py --include integration,performance """ import argparse import json import subprocess import sys from datetime import datetime from pathlib import Path from typing import Any, Dict, List, Optional class TestReportGenerator: """Generates comprehensive test reports from multiple test suites.""" def __init__(self, output: str = "test-report.json", format_type: str = "json", include: Optional[List[str]] = None): self.output = Path(output) self.format_type = format_type self.include = include or ["integration", "contract", "performance", "quality"] self.report_data = { "generated_at": datetime.utcnow().isoformat() + "Z", "test_suites": {}, "summary": {} } def run_test_suite(self, suite_name: str, test_path: str, extra_args: Optional[List[str]] = None) -> Dict[str, Any]: """Run a test suite and capture results. Args: suite_name: Name of the test suite test_path: Path to tests extra_args: Additional pytest arguments Returns: Dictionary with test results """ print(f"Running {suite_name} tests...") cmd = ["pytest", "-v", "--tb=short", test_path] if extra_args: cmd.extend(extra_args) result = subprocess.run( cmd, capture_output=True, text=True, cwd=Path(__file__).parent.parent.parent.parent ) # Parse output for test counts output = result.stdout + result.stderr lines = output.split('\n') passed = failed = skipped = 0 duration = 0.0 for line in lines: if " passed" in line: try: parts = line.split() for i, part in enumerate(parts): if part == "passed": passed = int(parts[i-1]) elif part == "failed": failed = int(parts[i-1]) elif part == "skipped": skipped = int(parts[i-1]) except (ValueError, IndexError): pass if "in" in line and "s" in line: try: duration_str = line.split("in ")[1].split("s")[0].strip() duration = float(duration_str) except (ValueError, IndexError): pass return { "total": passed + failed + skipped, "passed": passed, "failed": failed, "skipped": skipped, "duration": duration, "exit_code": result.returncode, "output": output if result.returncode != 0 else "" } def generate_integration_report(self) -> Dict[str, Any]: """Generate integration test report.""" if "integration" not in self.include: return {} result = self.run_test_suite( "Integration", "tests/integration/workflows/" ) return { "type": "integration", "tests": result, "coverage": self._get_coverage_data() } def generate_contract_report(self) -> Dict[str, Any]: """Generate contract test report.""" if "contract" not in self.include: return {} result = self.run_test_suite( "Contract", "tests/integration/contracts/", ["-m", "contract"] ) return { "type": "contract", "tests": result, "schema_validations": result.get("passed", 0) } def generate_performance_report(self) -> Dict[str, Any]: """Generate performance test report.""" if "performance" not in self.include: return {} result = self.run_test_suite( "Performance", "tests/integration/performance/", ["--benchmark-only"] ) return { "type": "performance", "tests": result, "benchmarks": self._parse_benchmark_data(result.get("output", "")) } def generate_quality_report(self) -> Dict[str, Any]: """Generate quality test report.""" if "quality" not in self.include: return {} result = self.run_test_suite( "Quality", "tests/integration/quality/" ) return { "type": "quality", "tests": result, "flakiness_rate": self._calculate_flakiness_rate(result) } def _get_coverage_data(self) -> Dict[str, float]: """Extract coverage data if available.""" coverage_file = Path("tests/coverage_reports/metrics/coverage.json") if not coverage_file.exists(): return {} try: with open(coverage_file) as f: data = json.load(f) return { "line_coverage": data.get("totals", {}).get("percent_covered", 0), "branch_coverage": data.get("totals", {}).get("percent_covered", 0) } except (json.JSONDecodeError, KeyError): return {} def _parse_benchmark_data(self, output: str) -> Dict[str, Any]: """Parse benchmark data from pytest-benchmark output.""" benchmarks = {} lines = output.split('\n') for line in lines: if "P50" in line or "P95" in line or "P99" in line: parts = line.split() if len(parts) >= 4: name = parts[0] benchmarks[name] = { "p50": float(parts[1]) if len(parts) > 1 else 0, "p95": float(parts[2]) if len(parts) > 2 else 0, "p99": float(parts[3]) if len(parts) > 3 else 0 } return benchmarks def _calculate_flakiness_rate(self, result: Dict[str, Any]) -> float: """Calculate flakiness rate from test results.""" total = result.get("total", 0) failed = result.get("failed", 0) if total == 0: return 0.0 return round((failed / total) * 100, 2) def generate_summary(self) -> Dict[str, Any]: """Generate overall summary from all test suites.""" total_tests = 0 total_passed = 0 total_failed = 0 total_duration = 0.0 for suite_data in self.report_data["test_suites"].values(): tests = suite_data.get("tests", {}) total_tests += tests.get("total", 0) total_passed += tests.get("passed", 0) total_failed += tests.get("failed", 0) total_duration += tests.get("duration", 0) pass_rate = round((total_passed / total_tests * 100) if total_tests > 0 else 0, 2) return { "total_tests": total_tests, "total_passed": total_passed, "total_failed": total_failed, "pass_rate": pass_rate, "total_duration": round(total_duration, 2) } def generate(self) -> None: """Generate the complete test report.""" # Generate individual suite reports integration = self.generate_integration_report() if integration: self.report_data["test_suites"]["integration"] = integration contract = self.generate_contract_report() if contract: self.report_data["test_suites"]["contract"] = contract performance = self.generate_performance_report() if performance: self.report_data["test_suites"]["performance"] = performance quality = self.generate_quality_report() if quality: self.report_data["test_suites"]["quality"] = quality # Generate summary self.report_data["summary"] = self.generate_summary() # Write output self._write_output() def _write_output(self) -> None: """Write report in specified format.""" if self.format_type == "json": self._write_json() elif self.format_type == "html": self._write_html() elif self.format_type == "markdown": self._write_markdown() else: print(f"Unknown format: {self.format_type}") sys.exit(1) def _write_json(self) -> None: """Write JSON report.""" with open(self.output, 'w') as f: json.dump(self.report_data, f, indent=2) print(f"✓ JSON report written to {self.output}") def _write_html(self) -> None: """Write HTML report.""" html = f"""
Generated: {self.report_data['generated_at']}
Total Tests: {self.report_data['summary']['total_tests']}
Passed: {self.report_data['summary']['total_passed']}
Failed: {self.report_data['summary']['total_failed']}
Pass Rate: {self.report_data['summary']['pass_rate']}%
Duration: {self.report_data['summary']['total_duration']}s
Total: {tests.get('total', 0)}
Passed: {tests.get('passed', 0)}
Failed: {tests.get('failed', 0)}
Duration: {tests.get('duration', 0)}s