Download tests/fuzzing/scripts/aggregate_crash_reports.py from techprotrade/annator-command-center: direct link, hf CLI and curl.
- Browser
- Download file 9.41 kB
-
https://huggingface.co/spaces/techprotrade/annator-command-center/resolve/main/tests/fuzzing/scripts/aggregate_crash_reports.py
- Command line
-
hf download hf://spaces/techprotrade/annator-command-center/tests/fuzzing/scripts/aggregate_crash_reports.py
-
curl -L -o aggregate_crash_reports.py https://huggingface.co/spaces/techprotrade/annator-command-center/resolve/main/tests/fuzzing/scripts/aggregate_crash_reports.py
9.41 kB
| #!/usr/bin/env python3 | |
| """ | |
| Crash Report Aggregation Script | |
| This script aggregates crash reports from multiple fuzzing campaigns, | |
| generates markdown and JSON reports with crash statistics and trends. | |
| Usage: | |
| python aggregate_crash_reports.py --crash-dirs "backend/tests/fuzzing/campaigns/crashes/*" | |
| python aggregate_crash_reports.py --crash-dirs "crashes/*" --output report.md | |
| Environment Variables: | |
| None | |
| """ | |
| import argparse | |
| import glob | |
| import json | |
| import os | |
| import sys | |
| from datetime import datetime | |
| from pathlib import Path | |
| from typing import Dict, List, Any | |
| # Add backend to path | |
| sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent)) | |
| from tests.fuzzing.campaigns.crash_deduplicator import CrashDeduplicator | |
| def aggregate_crashes(crash_dirs: List[Path]) -> Dict[str, List[Path]]: | |
| """ | |
| Aggregate crashes from multiple campaign directories. | |
| Args: | |
| crash_dirs: List of crash directory paths | |
| Returns: | |
| Dict mapping signature_hash to list of crash files | |
| """ | |
| deduplicator = CrashDeduplicator() | |
| crashes_by_signature = {} | |
| for crash_dir in crash_dirs: | |
| if not crash_dir.exists(): | |
| print(f"Warning: Crash directory not found: {crash_dir}") | |
| continue | |
| print(f"Aggregating crashes from: {crash_dir}") | |
| # Deduplicate crashes in this directory | |
| dir_crashes = deduplicator.deduplicate_crashes(crash_dir) | |
| # Merge into overall crashes_by_signature | |
| for signature_hash, crash_files in dir_crashes.items(): | |
| if signature_hash not in crashes_by_signature: | |
| crashes_by_signature[signature_hash] = [] | |
| crashes_by_signature[signature_hash].extend(crash_files) | |
| return crashes_by_signature | |
| def generate_report(crashes_by_signature: Dict[str, List[Path]], output_path: str, crash_dirs: List[Path] = None): | |
| """ | |
| Generate markdown and JSON crash reports. | |
| Args: | |
| crashes_by_signature: Dict mapping signature_hash to list of crash files | |
| output_path: Path to output markdown report | |
| crash_dirs: List of crash directories (for context) | |
| """ | |
| # Calculate statistics | |
| total_crashes = sum(len(files) for files in crashes_by_signature.values()) | |
| unique_crashes = len(crashes_by_signature) | |
| # Group crashes by endpoint (extract from directory name) | |
| crashes_by_endpoint = {} | |
| for signature_hash, crash_files in crashes_by_signature.items(): | |
| for crash_file in crash_files: | |
| # Extract endpoint from path: .../crashes/POST_api_auth_login_2026-03-24/*.input | |
| dir_name = crash_file.parent.name | |
| # Parse endpoint from directory name (remove timestamp) | |
| parts = dir_name.split("_") | |
| endpoint_parts = [] | |
| for part in parts: | |
| if part.isdigit() or part.startswith("2026-"): | |
| break | |
| endpoint_parts.append(part) | |
| endpoint = "_".join(endpoint_parts).replace("_", " ") | |
| if endpoint not in crashes_by_endpoint: | |
| crashes_by_endpoint[endpoint] = {"files": [], "unique_signatures": set()} | |
| crashes_by_endpoint[endpoint]["files"].extend(crash_files) | |
| crashes_by_endpoint[endpoint]["unique_signatures"].add(signature_hash) | |
| # Get top crash signatures by frequency | |
| signature_summary = [] | |
| for signature_hash, crash_files in crashes_by_signature.items(): | |
| signature_summary.append({ | |
| "signature_hash": signature_hash, | |
| "count": len(crash_files), | |
| "example_crash": str(crash_files[0]) | |
| }) | |
| signature_summary.sort(key=lambda x: x["count"], reverse=True) | |
| # Generate markdown report | |
| markdown_lines = [] | |
| markdown_lines.append("# Fuzzing Crash Report") | |
| markdown_lines.append("") | |
| markdown_lines.append(f"**Generated:** {datetime.utcnow().isoformat()}Z") | |
| markdown_lines.append("") | |
| # Summary section | |
| markdown_lines.append("## Summary") | |
| markdown_lines.append("") | |
| markdown_lines.append(f"- **Total crashes:** {total_crashes}") | |
| markdown_lines.append(f"- **Unique crashes:** {unique_crashes}") | |
| markdown_lines.append(f"- **Affected endpoints:** {len(crashes_by_endpoint)}") | |
| markdown_lines.append("") | |
| # Crashes by endpoint | |
| markdown_lines.append("## Crashes by Endpoint") | |
| markdown_lines.append("") | |
| for endpoint, data in sorted(crashes_by_endpoint.items()): | |
| total = len(data["files"]) | |
| unique = len(data["unique_signatures"]) | |
| markdown_lines.append(f"### {endpoint}") | |
| markdown_lines.append("") | |
| markdown_lines.append(f"- Total crashes: {total}") | |
| markdown_lines.append(f"- Unique crashes: {unique}") | |
| markdown_lines.append("") | |
| # Top crash signatures | |
| markdown_lines.append("## Top Crash Signatures") | |
| markdown_lines.append("") | |
| for i, sig in enumerate(signature_summary[:20], 1): | |
| markdown_lines.append(f"### {i}. Signature: `{sig['signature_hash'][:16]}...`") | |
| markdown_lines.append("") | |
| markdown_lines.append(f"- **Frequency:** {sig['count']} crashes") | |
| markdown_lines.append(f"- **Example:** `{sig['example_crash']}`") | |
| markdown_lines.append("") | |
| # Bug filing status | |
| markdown_lines.append("## Bug Filing Status") | |
| markdown_lines.append("") | |
| markdown_lines.append("Bug filing status is tracked by GitHub issues created via BugFilingService.") | |
| markdown_lines.append("Check GitHub repository for filed bugs with label `bug-discovery`.") | |
| markdown_lines.append("") | |
| # Trend analysis (if baseline exists) | |
| if crash_dirs: | |
| markdown_lines.append("## Trend Analysis") | |
| markdown_lines.append("") | |
| markdown_lines.append(f"Crash directories analyzed: {len(crash_dirs)}") | |
| markdown_lines.append("") | |
| for crash_dir in crash_dirs: | |
| markdown_lines.append(f"- `{crash_dir}`") | |
| markdown_lines.append("") | |
| markdown_lines.append("*Trend comparison with previous week requires baseline data.*") | |
| markdown_lines.append("") | |
| # Write markdown report | |
| with open(output_path, "w") as f: | |
| f.write("\n".join(markdown_lines)) | |
| print(f"Markdown report generated: {output_path}") | |
| # Generate JSON summary | |
| json_output_path = output_path.replace(".md", ".json") | |
| json_data = { | |
| "generated_at": datetime.utcnow().isoformat() + "Z", | |
| "summary": { | |
| "total_crashes": total_crashes, | |
| "unique_crashes": unique_crashes, | |
| "affected_endpoints": len(crashes_by_endpoint) | |
| }, | |
| "crashes_by_endpoint": { | |
| endpoint: { | |
| "total_crashes": len(data["files"]), | |
| "unique_crashes": len(data["unique_signatures"]) | |
| } | |
| for endpoint, data in crashes_by_endpoint.items() | |
| }, | |
| "top_signatures": signature_summary[:20] | |
| } | |
| with open(json_output_path, "w") as f: | |
| json.dump(json_data, f, indent=2) | |
| print(f"JSON summary generated: {json_output_path}") | |
| # Print summary to stdout | |
| print("\n" + "="*60) | |
| print("Crash Aggregation Summary") | |
| print("="*60) | |
| print(f"Total crashes: {total_crashes}") | |
| print(f"Unique crashes: {unique_crashes}") | |
| print(f"Affected endpoints: {len(crashes_by_endpoint)}") | |
| print("="*60) | |
| def main(): | |
| """Main entry point.""" | |
| parser = argparse.ArgumentParser( | |
| description="Aggregate crash reports from fuzzing campaigns", | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| epilog=""" | |
| Examples: | |
| # Aggregate crashes from all campaign directories | |
| python aggregate_crash_reports.py --crash-dirs "backend/tests/fuzzing/campaigns/crashes/*" | |
| # Specify output file | |
| python aggregate_crash_reports.py --crash-dirs "crashes/*" --output /tmp/report.md | |
| # Multiple directories | |
| python aggregate_crash_reports.py --crash-dirs "crashes/*" --crash-dirs "old/crashes/*" | |
| """ | |
| ) | |
| parser.add_argument( | |
| "--crash-dirs", | |
| action="append", | |
| required=True, | |
| help="Glob pattern for crash directories (can be specified multiple times)" | |
| ) | |
| parser.add_argument( | |
| "--output", | |
| type=str, | |
| default="fuzzing-report.md", | |
| help="Output report path (default: fuzzing-report.md)" | |
| ) | |
| args = parser.parse_args() | |
| # Expand glob patterns | |
| crash_dirs = [] | |
| for pattern in args.crash_dirs: | |
| expanded = glob.glob(pattern) | |
| if not expanded: | |
| print(f"Warning: No directories found for pattern: {pattern}") | |
| crash_dirs.extend([Path(d) for d in expanded]) | |
| if not crash_dirs: | |
| print("Error: No crash directories found") | |
| sys.exit(1) | |
| print(f"Found {len(crash_dirs)} crash directory(s)") | |
| # Aggregate crashes | |
| crashes_by_signature = aggregate_crashes(crash_dirs) | |
| if not crashes_by_signature: | |
| print("No crashes found in any directory") | |
| # Generate empty report | |
| generate_report({}, args.output, crash_dirs) | |
| sys.exit(0) | |
| # Generate report | |
| generate_report(crashes_by_signature, args.output, crash_dirs) | |
| # Exit code: 0 if success, 1 if no crashes | |
| total_crashes = sum(len(files) for files in crashes_by_signature.values()) | |
| if total_crashes == 0: | |
| sys.exit(1) | |
| else: | |
| sys.exit(0) | |
| if __name__ == "__main__": | |
| main() | |