Spaces:
Sleeping
Sleeping
Download tests/scripts/analyze_coverage_gaps.py from techprotrade/annator-command-center: direct link, hf CLI and curl.
- Browser
- Download file 28.6 kB
-
https://huggingface.co/spaces/techprotrade/annator-command-center/resolve/main/tests/scripts/analyze_coverage_gaps.py
- Command line
-
hf download hf://spaces/techprotrade/annator-command-center/tests/scripts/analyze_coverage_gaps.py
-
curl -L -o analyze_coverage_gaps.py https://huggingface.co/spaces/techprotrade/annator-command-center/resolve/main/tests/scripts/analyze_coverage_gaps.py
28.6 kB
| #!/usr/bin/env python3 | |
| """ | |
| Coverage Gap Analysis Script | |
| Analyzes pytest coverage reports to identify high-impact testing opportunities. | |
| Categorizes files by size, calculates coverage gaps, and prioritizes files for | |
| maximum coverage improvement. | |
| Usage: | |
| python3 tests/scripts/analyze_coverage_gaps.py --format all --output-dir tests/coverage_reports/metrics/ | |
| python3 tests/scripts/analyze_coverage_gaps.py --format json --output-dir /path/to/output | |
| python3 tests/scripts/analyze_coverage_gaps.py --format markdown --output-dir reports/ | |
| Output Files: | |
| - coverage_summary.json: Module-level aggregations and file tiers | |
| - priority_files_for_phases_12_13.json: Ranked file list for test planning | |
| - zero_coverage_analysis.json: Files with 0% coverage >100 lines | |
| - PHASE_11_COVERAGE_ANALYSIS_REPORT.md: Comprehensive markdown report | |
| Requirements: | |
| - Python 3.11+ | |
| - coverage.json from pytest-cov | |
| Author: Phase 11 Coverage Analysis | |
| Generated: 2026-02-15 | |
| """ | |
| import argparse | |
| import json | |
| import sys | |
| from datetime import datetime | |
| from pathlib import Path | |
| from typing import Any, Dict, List, Tuple | |
| def load_coverage_data(coverage_path: str) -> Dict[str, Any]: | |
| """ | |
| Load coverage.json from pytest-cov. | |
| Args: | |
| coverage_path: Path to coverage.json file | |
| Returns: | |
| Coverage data dictionary | |
| Raises: | |
| FileNotFoundError: If coverage.json doesn't exist | |
| json.JSONDecodeError: If coverage.json is invalid | |
| """ | |
| path = Path(coverage_path) | |
| if not path.exists(): | |
| raise FileNotFoundError( | |
| f"Coverage file not found: {coverage_path}\n" | |
| f"Run tests with coverage first: pytest --cov=backend --cov-report=json" | |
| ) | |
| with open(path, 'r') as f: | |
| return json.load(f) | |
| def categorize_file_by_size(lines: int) -> str: | |
| """ | |
| Categorize file by total lines of code. | |
| Args: | |
| lines: Total number of lines (statements) | |
| Returns: | |
| Tier category (Tier 1-5) | |
| """ | |
| if lines >= 500: | |
| return "Tier 1" # Highest impact | |
| elif lines >= 300: | |
| return "Tier 2" # High impact | |
| elif lines >= 200: | |
| return "Tier 3" # Medium impact | |
| elif lines >= 100: | |
| return "Tier 4" # Low impact | |
| else: | |
| return "Tier 5" # Minimal impact | |
| def calculate_metrics(filedata: Dict[str, Any]) -> Dict[str, float]: | |
| """ | |
| Calculate coverage metrics for a single file. | |
| Args: | |
| filedata: File coverage data from coverage.json | |
| Returns: | |
| Dictionary with calculated metrics | |
| """ | |
| total_lines = filedata['summary']['num_statements'] | |
| covered_lines = filedata['summary']['covered_lines'] | |
| coverage_pct = filedata['summary']['percent_covered'] | |
| # Coverage gap: lines that are NOT covered | |
| coverage_gap = total_lines - covered_lines | |
| # Potential gain: assuming 50% achievable coverage (Phase 8.6 proven target) | |
| potential_gain = coverage_gap * 0.5 | |
| # Priority score: uncovered ratio (higher = more priority) | |
| priority_score = coverage_gap / total_lines if total_lines > 0 else 0 | |
| return { | |
| 'total_lines': total_lines, | |
| 'covered_lines': covered_lines, | |
| 'coverage_pct': coverage_pct, | |
| 'coverage_gap': coverage_gap, | |
| 'potential_gain': potential_gain, | |
| 'priority_score': priority_score | |
| } | |
| def analyze_files(coverage_data: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """ | |
| Analyze all files in coverage report and calculate metrics. | |
| Args: | |
| coverage_data: Full coverage data dictionary | |
| Returns: | |
| List of file analysis results with metrics | |
| """ | |
| files_analysis = [] | |
| for filepath, filedata in coverage_data['files'].items(): | |
| metrics = calculate_metrics(filedata) | |
| tier = categorize_file_by_size(metrics['total_lines']) | |
| files_analysis.append({ | |
| 'file': filepath, | |
| 'tier': tier, | |
| **metrics | |
| }) | |
| return files_analysis | |
| def create_module_aggregation(files_analysis: List[Dict[str, Any]]) -> Dict[str, Any]: | |
| """ | |
| Aggregate metrics by module (core/, api/, tools/, etc.). | |
| Args: | |
| files_analysis: List of file analysis results | |
| Returns: | |
| Module-level aggregated metrics | |
| """ | |
| modules = {} | |
| for file_data in files_analysis: | |
| # Extract module from filepath (e.g., "core/workflow_engine.py" -> "core") | |
| parts = file_data['file'].split('/') | |
| module = parts[0] if len(parts) > 1 else 'other' | |
| if module not in modules: | |
| modules[module] = { | |
| 'total_lines': 0, | |
| 'covered_lines': 0, | |
| 'files': [] | |
| } | |
| modules[module]['total_lines'] += file_data['total_lines'] | |
| modules[module]['covered_lines'] += file_data['covered_lines'] | |
| modules[module]['files'].append(file_data) | |
| # Calculate module percentages | |
| for module in modules.values(): | |
| module['coverage_pct'] = ( | |
| module['covered_lines'] / module['total_lines'] * 100 | |
| if module['total_lines'] > 0 else 0 | |
| ) | |
| module['coverage_gap'] = module['total_lines'] - module['covered_lines'] | |
| return modules | |
| def get_high_priority_files(files_analysis: List[Dict[str, Any]]) -> List[Dict[str, Any]]: | |
| """ | |
| Get high-priority files for testing (Tier 1-3, sorted by coverage gap). | |
| Args: | |
| files_analysis: List of file analysis results | |
| Returns: | |
| Sorted list of high-priority files | |
| """ | |
| # Filter to Tier 1-3 (files >= 200 lines) | |
| high_priority = [f for f in files_analysis if f['tier'] in ['Tier 1', 'Tier 2', 'Tier 3']] | |
| # Sort by coverage gap (largest gap first) | |
| high_priority.sort(key=lambda x: x['coverage_gap'], reverse=True) | |
| return high_priority | |
| def get_zero_coverage_files(files_analysis: List[Dict[str, Any]]) -> List[Dict[str, Any]]: | |
| """ | |
| Get files with 0% coverage and >100 lines. | |
| Args: | |
| files_analysis: List of file analysis results | |
| Returns: | |
| List of zero-coverage files | |
| """ | |
| zero_coverage = [ | |
| f for f in files_analysis | |
| if f['coverage_pct'] == 0 and f['total_lines'] >= 100 | |
| ] | |
| # Sort by file size (largest first) | |
| zero_coverage.sort(key=lambda x: x['total_lines'], reverse=True) | |
| return zero_coverage | |
| def estimate_test_complexity(file_data: Dict[str, Any]) -> str: | |
| """ | |
| Estimate test complexity based on file size and coverage. | |
| Args: | |
| file_data: File analysis data | |
| Returns: | |
| Complexity level (low, medium, high) | |
| """ | |
| lines = file_data['total_lines'] | |
| if lines < 150: | |
| return "low" # <30 tests | |
| elif lines < 400: | |
| return "medium" # 30-60 tests | |
| else: | |
| return "high" # >60 tests | |
| def recommend_test_type(filepath: str) -> str: | |
| """ | |
| Recommend test type based on file characteristics. | |
| Args: | |
| filepath: File path | |
| Returns: | |
| Recommended test type (unit, integration, property, e2e) | |
| """ | |
| # Property tests for stateful logic | |
| if any(keyword in filepath for keyword in [ | |
| 'workflow', 'engine', 'handler', 'coordinator', 'executor' | |
| ]): | |
| return "property" | |
| # Integration tests for API endpoints | |
| if any(keyword in filepath for keyword in [ | |
| 'endpoint', 'route', 'api' | |
| ]): | |
| return "integration" | |
| # Unit tests for isolated logic | |
| if any(keyword in filepath for keyword in [ | |
| 'service', 'util', 'helper', 'manager' | |
| ]): | |
| return "unit" | |
| # Default to unit tests | |
| return "unit" | |
| def create_coverage_summary( | |
| files_analysis: List[Dict[str, Any]], | |
| modules: Dict[str, Any] | |
| ) -> Dict[str, Any]: | |
| """ | |
| Create coverage summary JSON output. | |
| Args: | |
| files_analysis: List of file analysis results | |
| modules: Module-level aggregated metrics | |
| Returns: | |
| Coverage summary dictionary | |
| """ | |
| # Calculate overall metrics | |
| total_lines = sum(f['total_lines'] for f in files_analysis) | |
| total_covered = sum(f['covered_lines'] for f in files_analysis) | |
| overall_pct = (total_covered / total_lines * 100) if total_lines > 0 else 0 | |
| # Get high-priority files | |
| high_priority = get_high_priority_files(files_analysis) | |
| zero_coverage = get_zero_coverage_files(files_analysis) | |
| # Organize by tier | |
| files_by_tier = { | |
| 'Tier 1': [f for f in files_analysis if f['tier'] == 'Tier 1'], | |
| 'Tier 2': [f for f in files_analysis if f['tier'] == 'Tier 2'], | |
| 'Tier 3': [f for f in files_analysis if f['tier'] == 'Tier 3'], | |
| 'Tier 4': [f for f in files_analysis if f['tier'] == 'Tier 4'], | |
| 'Tier 5': [f for f in files_analysis if f['tier'] == 'Tier 5'], | |
| } | |
| return { | |
| 'generated_at': datetime.utcnow().isoformat() + 'Z', | |
| 'overall': { | |
| 'percent_covered': round(overall_pct, 2), | |
| 'covered_lines': total_covered, | |
| 'total_lines': total_lines, | |
| 'coverage_gap': total_lines - total_covered | |
| }, | |
| 'modules': { | |
| module: { | |
| 'percent_covered': round(data['coverage_pct'], 2), | |
| 'covered_lines': data['covered_lines'], | |
| 'total_lines': data['total_lines'], | |
| 'coverage_gap': data['coverage_gap'], | |
| 'file_count': len(data['files']) | |
| } | |
| for module, data in modules.items() | |
| }, | |
| 'files_by_size': { | |
| tier: { | |
| 'file_count': len(files), | |
| 'total_lines': sum(f['total_lines'] for f in files), | |
| 'covered_lines': sum(f['covered_lines'] for f in files), | |
| 'avg_coverage_pct': round( | |
| sum(f['covered_lines'] for f in files) / | |
| sum(f['total_lines'] for f in files) * 100 | |
| if sum(f['total_lines'] for f in files) > 0 else 0, | |
| 2 | |
| ) | |
| } | |
| for tier, files in files_by_tier.items() | |
| }, | |
| 'high_priority_files': [ | |
| { | |
| 'file': f['file'], | |
| 'lines': f['total_lines'], | |
| 'coverage_pct': round(f['coverage_pct'], 2), | |
| 'uncovered_lines': f['coverage_gap'], | |
| 'potential_gain': round(f['potential_gain']), | |
| 'priority_score': round(f['priority_score'], 3) | |
| } | |
| for f in high_priority[:50] # Top 50 | |
| ], | |
| 'zero_coverage_files': [ | |
| { | |
| 'file': f['file'], | |
| 'lines': f['total_lines'], | |
| 'estimated_gain_lines': round(f['total_lines'] * 0.5) | |
| } | |
| for f in zero_coverage | |
| ] | |
| } | |
| def create_priority_files_list( | |
| high_priority: List[Dict[str, Any]], | |
| zero_coverage: List[Dict[str, Any]] | |
| ) -> Dict[str, Any]: | |
| """ | |
| Create prioritized file list for Phases 12-13. | |
| Args: | |
| high_priority: List of high-priority files | |
| zero_coverage: List of zero-coverage files | |
| Returns: | |
| Priority files dictionary | |
| """ | |
| # Calculate overall metrics | |
| total_lines = sum(f['total_lines'] for f in high_priority) | |
| total_covered = sum(f['covered_lines'] for f in high_priority) | |
| overall_pct = (total_covered / total_lines * 100) if total_lines > 0 else 0 | |
| # Split into Phase 12 (Tier 1, <20% coverage) and Phase 13 (Tier 2-3, <30%) | |
| phase_12_files = [ | |
| f for f in high_priority | |
| if f['tier'] == 'Tier 1' and f['coverage_pct'] < 20 | |
| ][:20] # Top 20 files | |
| phase_13_files = [ | |
| f for f in high_priority | |
| if f not in phase_12_files and f['coverage_pct'] < 30 | |
| ][:20] # Next 20 files | |
| # Add zero-coverage files to Phase 13 | |
| phase_13_files.extend(zero_coverage[:10]) | |
| return { | |
| 'generated_at': datetime.utcnow().isoformat() + 'Z', | |
| 'current_coverage': { | |
| 'percent': round(overall_pct, 2), | |
| 'covered_lines': total_covered, | |
| 'total_lines': total_lines | |
| }, | |
| 'target_coverage': { | |
| 'percent': 80, | |
| 'required_lines': int(total_lines * 0.8), | |
| 'gap_lines': total_lines - int(total_lines * 0.8) | |
| }, | |
| 'phases': { | |
| '12': { | |
| 'target_percent': 28, | |
| 'target_gain': 5.2, | |
| 'files': [ | |
| { | |
| 'rank': i + 1, | |
| 'file': f['file'], | |
| 'lines': f['total_lines'], | |
| 'current_percent': round(f['coverage_pct'], 2), | |
| 'uncovered_lines': f['coverage_gap'], | |
| 'estimated_tests_needed': max(30, int(f['total_lines'] / 20)), | |
| 'recommended_test_type': recommend_test_type(f['file']), | |
| 'coverage_complexity': estimate_test_complexity(f), | |
| 'tier': f['tier'] | |
| } | |
| for i, f in enumerate(phase_12_files) | |
| ] | |
| }, | |
| '13': { | |
| 'target_percent': 35, | |
| 'target_gain': 7.0, | |
| 'files': [ | |
| { | |
| 'rank': i + 1, | |
| 'file': f['file'], | |
| 'lines': f['total_lines'], | |
| 'current_percent': round(f['coverage_pct'], 2), | |
| 'uncovered_lines': f['coverage_gap'], | |
| 'estimated_tests_needed': max(30, int(f['total_lines'] / 20)), | |
| 'recommended_test_type': recommend_test_type(f['file']), | |
| 'coverage_complexity': estimate_test_complexity(f), | |
| 'tier': f['tier'] | |
| } | |
| for i, f in enumerate(phase_13_files) | |
| ] | |
| } | |
| }, | |
| 'zero_coverage_quick_wins': [ | |
| { | |
| 'file': f['file'], | |
| 'lines': f['total_lines'], | |
| 'estimated_gain_lines': round(f['total_lines'] * 0.5), | |
| 'recommended_test_type': recommend_test_type(f['file']) | |
| } | |
| for f in zero_coverage[:30] # Top 30 | |
| ] | |
| } | |
| def create_zero_coverage_analysis(zero_coverage: List[Dict[str, Any]]) -> Dict[str, Any]: | |
| """ | |
| Create zero coverage analysis JSON. | |
| Args: | |
| zero_coverage: List of zero-coverage files | |
| Returns: | |
| Zero coverage analysis dictionary | |
| """ | |
| return { | |
| 'generated_at': datetime.utcnow().isoformat() + 'Z', | |
| 'total_zero_coverage_files': len(zero_coverage), | |
| 'total_lines_uncovered': sum(f['total_lines'] for f in zero_coverage), | |
| 'estimated_total_gain': sum(f['total_lines'] * 0.5 for f in zero_coverage), | |
| 'files': [ | |
| { | |
| 'file': f['file'], | |
| 'lines': f['total_lines'], | |
| 'estimated_gain_lines': round(f['total_lines'] * 0.5), | |
| 'recommended_test_type': recommend_test_type(f['file']), | |
| 'complexity': estimate_test_complexity(f) | |
| } | |
| for f in zero_coverage | |
| ] | |
| } | |
| def generate_markdown_report( | |
| coverage_summary: Dict[str, Any], | |
| priority_files: Dict[str, Any], | |
| modules: Dict[str, Any] | |
| ) -> str: | |
| """ | |
| Generate comprehensive markdown report. | |
| Args: | |
| coverage_summary: Coverage summary data | |
| priority_files: Priority files data | |
| modules: Module aggregated data | |
| Returns: | |
| Markdown report content | |
| """ | |
| overall = coverage_summary['overall'] | |
| md = f"""# Phase 11 Coverage Analysis Report | |
| **Generated:** {coverage_summary['generated_at']} | |
| **Purpose:** Identify highest-impact testing opportunities for Phases 12-13 | |
| --- | |
| ## Executive Summary | |
| ### Current Coverage Status | |
| | Metric | Value | | |
| |--------|-------| | |
| | Overall Coverage | {overall['percent_covered']}% | | |
| | Covered Lines | {overall['covered_lines']:,} | | |
| | Total Lines | {overall['total_lines']:,} | | |
| | Coverage Gap | {overall['coverage_gap']:,} lines | | |
| | Target Coverage (80%) | {int(overall['total_lines'] * 0.8):,} lines | | |
| | Remaining Gap | {int(overall['total_lines'] * 0.8) - overall['covered_lines']:,} lines | | |
| ### File Distribution by Size | |
| | Tier | Size Range | File Count | Total Lines | Avg Coverage | | |
| |------|------------|------------|-------------|--------------| | |
| | Tier 1 | ≥500 lines | {coverage_summary['files_by_size']['Tier 1']['file_count']} | {coverage_summary['files_by_size']['Tier 1']['total_lines']:,} | {coverage_summary['files_by_size']['Tier 1']['avg_coverage_pct']}% | | |
| | Tier 2 | 300-499 lines | {coverage_summary['files_by_size']['Tier 2']['file_count']} | {coverage_summary['files_by_size']['Tier 2']['total_lines']:,} | {coverage_summary['files_by_size']['Tier 2']['avg_coverage_pct']}% | | |
| | Tier 3 | 200-299 lines | {coverage_summary['files_by_size']['Tier 3']['file_count']} | {coverage_summary['files_by_size']['Tier 3']['total_lines']:,} | {coverage_summary['files_by_size']['Tier 3']['avg_coverage_pct']}% | | |
| | Tier 4 | 100-199 lines | {coverage_summary['files_by_size']['Tier 4']['file_count']} | {coverage_summary['files_by_size']['Tier 4']['total_lines']:,} | {coverage_summary['files_by_size']['Tier 4']['avg_coverage_pct']}% | | |
| | Tier 5 | <100 lines | {coverage_summary['files_by_size']['Tier 5']['file_count']} | {coverage_summary['files_by_size']['Tier 5']['total_lines']:,} | {coverage_summary['files_by_size']['Tier 5']['avg_coverage_pct']}% | | |
| --- | |
| ## Top 20 High-Impact Files | |
| Ranked by coverage gap (uncovered lines). **Target: 50% coverage per file** (Phase 8.6 proven sustainable). | |
| | Rank | File | Lines | Current % | Uncovered | Priority | Tier | Test Type | Complexity | | |
| |------|------|-------|-----------|-----------|----------|------|-----------|------------| | |
| """ | |
| for i, file_data in enumerate(coverage_summary['high_priority_files'][:20], 1): | |
| filepath = file_data['file'] | |
| lines = file_data['lines'] | |
| pct = file_data['coverage_pct'] | |
| uncovered = file_data['uncovered_lines'] | |
| priority = file_data['priority_score'] | |
| # Determine tier and test type | |
| tier = "Tier 1" if lines >= 500 else "Tier 2" if lines >= 300 else "Tier 3" | |
| test_type = recommend_test_type(filepath) | |
| complexity = "high" if lines >= 400 else "medium" if lines >= 150 else "low" | |
| md += f"| {i} | {filepath} | {lines} | {pct}% | {uncovered} | {priority:.3f} | {tier} | {test_type} | {complexity} |\n" | |
| md += f""" | |
| --- | |
| ## Zero Coverage Quick Wins | |
| Files with **0% coverage** and >100 lines. Testing these to 50% coverage provides fast wins. | |
| **Total zero-coverage files >100 lines:** {len(coverage_summary['zero_coverage_files'])} | |
| | File | Lines | Est. Gain (50%) | Test Type | Complexity | | |
| |------|-------|-----------------|-----------|------------| | |
| """ | |
| for file_data in coverage_summary['zero_coverage_files'][:25]: | |
| filepath = file_data['file'] | |
| lines = file_data['lines'] | |
| gain = file_data['estimated_gain_lines'] | |
| test_type = recommend_test_type(filepath) | |
| complexity = "high" if lines >= 400 else "medium" if lines >= 150 else "low" | |
| md += f"| {filepath} | {lines} | {gain} | {test_type} | {complexity} |\n" | |
| md += f""" | |
| --- | |
| ## Module Breakdown | |
| ### Core Module | |
| **Files:** {len(modules.get('core', {}).get('files', []))} | |
| | Metric | Value | | |
| |--------|-------| | |
| | Coverage | {modules.get('core', {}).get('coverage_pct', 0):.1f}% | | |
| | Covered Lines | {modules.get('core', {}).get('covered_lines', 0):,} | | |
| | Total Lines | {modules.get('core', {}).get('total_lines', 0):,} | | |
| | Coverage Gap | {modules.get('core', {}).get('coverage_gap', 0):,} | | |
| **Top 5 Gaps in core/:** | |
| """ | |
| core_files = sorted( | |
| modules.get('core', {}).get('files', []), | |
| key=lambda x: x['coverage_gap'], | |
| reverse=True | |
| )[:5] | |
| for i, f in enumerate(core_files, 1): | |
| md += f"{i}. {f['file']}: {f['coverage_gap']} lines uncovered ({f['coverage_pct']:.1f}%)\n" | |
| md += f""" | |
| ### API Module | |
| **Files:** {len(modules.get('api', {}).get('files', []))} | |
| | Metric | Value | | |
| |--------|-------| | |
| | Coverage | {modules.get('api', {}).get('coverage_pct', 0):.1f}% | | |
| | Covered Lines | {modules.get('api', {}).get('covered_lines', 0):,} | | |
| | Total Lines | {modules.get('api', {}).get('total_lines', 0):,} | | |
| | Coverage Gap | {modules.get('api', {}).get('coverage_gap', 0):,} | | |
| **Top 5 Gaps in api/:** | |
| """ | |
| api_files = sorted( | |
| modules.get('api', {}).get('files', []), | |
| key=lambda x: x['coverage_gap'], | |
| reverse=True | |
| )[:5] | |
| for i, f in enumerate(api_files, 1): | |
| md += f"{i}. {f['file']}: {f['coverage_gap']} lines uncovered ({f['coverage_pct']:.1f}%)\n" | |
| md += f""" | |
| ### Tools Module | |
| **Files:** {len(modules.get('tools', {}).get('files', []))} | |
| | Metric | Value | | |
| |--------|-------| | |
| | Coverage | {modules.get('tools', {}).get('coverage_pct', 0):.1f}% | | |
| | Covered Lines | {modules.get('tools', {}).get('covered_lines', 0):,} | | |
| | Total Lines | {modules.get('tools', {}).get('total_lines', 0):,} | | |
| | Coverage Gap | {modules.get('tools', {}).get('coverage_gap', 0):,} | | |
| **Top 5 Gaps in tools/:** | |
| """ | |
| tools_files = sorted( | |
| modules.get('tools', {}).get('files', []), | |
| key=lambda x: x['coverage_gap'], | |
| reverse=True | |
| )[:5] | |
| for i, f in enumerate(tools_files, 1): | |
| md += f"{i}. {f['file']}: {f['coverage_gap']} lines uncovered ({f['coverage_pct']:.1f}%)\n" | |
| md += f""" | |
| --- | |
| ## Phase 12-13 Testing Strategy | |
| ### Strategy Overview | |
| Based on Phase 8.6 validation (3.38x velocity acceleration), this analysis prioritizes **high-impact files** for maximum coverage gain. | |
| **Key Principles:** | |
| - Target 50% average coverage per file (proven sustainable) | |
| - Prioritize largest files first (Tier 1 > Tier 2 > Tier 3) | |
| - Use appropriate test types (property, integration, unit) | |
| - 3-4 files per plan for focused execution | |
| ### Phase 12: Tier 1 Files | |
| **Target:** {priority_files['phases']['12']['target_percent']}% coverage (+{priority_files['phases']['12']['target_gain']} percentage points) | |
| **Focus:** Files ≥500 lines with <20% coverage | |
| **Estimated Plans:** 4-5 plans | |
| **Estimated Velocity:** +1.3-1.5% per plan | |
| **Files:** {len(priority_files['phases']['12']['files'])} files | |
| ### Phase 13: Tier 2-3 + Zero Coverage | |
| **Target:** {priority_files['phases']['13']['target_percent']}% coverage (+{priority_files['phases']['13']['target_gain']} percentage points) | |
| **Focus:** Files 300-500 lines, <30% coverage + zero-coverage quick wins | |
| **Estimated Plans:** 5-6 plans | |
| **Estimated Velocity:** +1.2-1.4% per plan | |
| **Files:** {len(priority_files['phases']['13']['files'])} files | |
| ### Test Type Recommendations | |
| | Test Type | When to Use | Examples | | |
| |-----------|-------------|----------| | |
| | **Property Tests** | Stateful logic, workflows, handlers | workflow_engine, byok_handler, coordinators | | |
| | **Integration Tests** | API endpoints, routes | atom_agent_endpoints, workflow_endpoints, API routes | | |
| | **Unit Tests** | Isolated logic, services, utilities | lancedb_handler, auto_document_ingestion, services | | |
| ### Execution Plan | |
| **Files Per Plan:** 3-4 high-impact files | |
| **Target Coverage Per File:** 50% (Phase 8.6 proven sustainable) | |
| **Estimated Velocity:** +1.5% per plan (maintaining Phase 8.6 acceleration) | |
| **Estimated Duration:** 4-6 hours per plan | |
| **Total Estimated Impact:** | |
| - Phase 12: +{priority_files['phases']['12']['target_gain']} percentage points | |
| - Phase 13: +{priority_files['phases']['13']['target_gain']} percentage points | |
| - Combined: +{priority_files['phases']['12']['target_gain'] + priority_files['phases']['13']['target_gain']} percentage points | |
| --- | |
| ## Recommendations | |
| 1. **Start with Tier 1 files** (Phase 12) - Highest ROI with 3.38x velocity acceleration | |
| 2. **Target 50% coverage per file** - Proven sustainable from Phase 8.6 | |
| 3. **Use appropriate test types** - Property tests for stateful logic, integration for APIs | |
| 4. **3-4 files per plan** - Focused execution without overwhelming complexity | |
| 5. **Leverage existing test infrastructure** - Property test patterns, AsyncMock, FastAPI TestClient | |
| --- | |
| ## Appendix: Data Sources | |
| - **Coverage Data:** `tests/coverage_reports/metrics/coverage.json` | |
| - **Analysis Script:** `tests/scripts/analyze_coverage_gaps.py` | |
| - **Priority Files:** `tests/coverage_reports/priority_files_for_phases_12_13.json` | |
| *Generated by Phase 11 Coverage Analysis - {datetime.utcnow().strftime('%Y-%m-%d')}* | |
| """ | |
| return md | |
| def main(): | |
| """Main entry point for coverage analysis script.""" | |
| parser = argparse.ArgumentParser( | |
| description='Analyze coverage gaps and prioritize testing opportunities', | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| epilog=__doc__ | |
| ) | |
| parser.add_argument( | |
| '--coverage-file', | |
| default='backend/tests/coverage_reports/metrics/coverage.json', | |
| help='Path to coverage.json (default: backend/tests/coverage_reports/metrics/coverage.json)' | |
| ) | |
| parser.add_argument( | |
| '--format', | |
| choices=['json', 'markdown', 'all'], | |
| default='all', | |
| help='Output format (default: all)' | |
| ) | |
| parser.add_argument( | |
| '--output-dir', | |
| default='backend/tests/coverage_reports/metrics/', | |
| help='Output directory for generated files (default: backend/tests/coverage_reports/metrics/)' | |
| ) | |
| args = parser.parse_args() | |
| try: | |
| # Load coverage data | |
| print(f"Loading coverage data from {args.coverage_file}...") | |
| coverage_data = load_coverage_data(args.coverage_file) | |
| print(f"Found {len(coverage_data['files'])} files") | |
| # Analyze files | |
| print("Analyzing coverage gaps...") | |
| files_analysis = analyze_files(coverage_data) | |
| modules = create_module_aggregation(files_analysis) | |
| high_priority = get_high_priority_files(files_analysis) | |
| zero_coverage = get_zero_coverage_files(files_analysis) | |
| # Create output directory | |
| output_dir = Path(args.output_dir) | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| # Generate outputs based on format | |
| if args.format in ['json', 'all']: | |
| print("Generating JSON files...") | |
| # Coverage summary | |
| coverage_summary = create_coverage_summary(files_analysis, modules) | |
| summary_path = output_dir / 'coverage_summary.json' | |
| with open(summary_path, 'w') as f: | |
| json.dump(coverage_summary, f, indent=2) | |
| print(f" Created: {summary_path}") | |
| # Priority files for Phases 12-13 | |
| priority_files = create_priority_files_list(high_priority, zero_coverage) | |
| priority_path = output_dir / 'priority_files_for_phases_12_13.json' | |
| with open(priority_path, 'w') as f: | |
| json.dump(priority_files, f, indent=2) | |
| print(f" Created: {priority_path}") | |
| # Zero coverage analysis | |
| zero_analysis = create_zero_coverage_analysis(zero_coverage) | |
| zero_path = output_dir / 'zero_coverage_analysis.json' | |
| with open(zero_path, 'w') as f: | |
| json.dump(zero_analysis, f, indent=2) | |
| print(f" Created: {zero_path}") | |
| if args.format in ['markdown', 'all']: | |
| print("Generating markdown report...") | |
| # Generate markdown report | |
| markdown_report = generate_markdown_report( | |
| coverage_summary if args.format in ['json', 'all'] else create_coverage_summary(files_analysis, modules), | |
| priority_files if args.format in ['json', 'all'] else create_priority_files_list(high_priority, zero_coverage), | |
| modules | |
| ) | |
| report_path = output_dir / 'PHASE_11_COVERAGE_ANALYSIS_REPORT.md' | |
| with open(report_path, 'w') as f: | |
| f.write(markdown_report) | |
| print(f" Created: {report_path}") | |
| print("\nAnalysis complete!") | |
| print(f"High-priority files identified: {len(high_priority)}") | |
| print(f"Zero-coverage files (>100 lines): {len(zero_coverage)}") | |
| return 0 | |
| except FileNotFoundError as e: | |
| print(f"Error: {e}", file=sys.stderr) | |
| return 1 | |
| except json.JSONDecodeError as e: | |
| print(f"Error: Invalid JSON in coverage file: {e}", file=sys.stderr) | |
| return 1 | |
| except Exception as e: | |
| print(f"Unexpected error: {e}", file=sys.stderr) | |
| import traceback | |
| traceback.print_exc() | |
| return 1 | |
| if __name__ == '__main__': | |
| sys.exit(main()) | |