chore: initial release v0.1.0
Runtime-agnostic oneshot broadcast channel for Rust.
This commit is contained in:
64 files changed
+23966
No files matched your search
Executable
+124
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate mutation testing trend report from mutation_history.json
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def load_history(history_file: Path) -> list:
|
||||
"""Load mutation testing history from JSON file."""
|
||||
if not history_file.exists():
|
||||
print(f"❌ History file not found: {history_file}")
|
||||
sys.exit(1)
|
||||
|
||||
with open(history_file) as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def format_timestamp(ts: str) -> str:
|
||||
"""Format ISO timestamp to readable date."""
|
||||
dt = datetime.fromisoformat(ts.replace('Z', '+00:00'))
|
||||
return dt.strftime('%Y-%m-%d %H:%M')
|
||||
|
||||
|
||||
def print_summary(history: list) -> None:
|
||||
"""Print summary statistics."""
|
||||
if not history:
|
||||
print("No mutation testing history found.")
|
||||
return
|
||||
|
||||
latest = history[-1]
|
||||
stats = latest['stats']
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print(" MUTATION TESTING SUMMARY")
|
||||
print("=" * 70)
|
||||
print(f"\nLatest Run: {format_timestamp(latest['timestamp'])}")
|
||||
print(f"Git Branch: {latest['git_branch']}")
|
||||
print(f"Git Commit: {latest['git_commit'][:8]}")
|
||||
print(f"\nCurrent Score: {stats['score']}%")
|
||||
print(f" Total Mutants: {stats['total_mutants']}")
|
||||
print(f" Viable: {stats['viable']}")
|
||||
print(f" Caught: {stats['caught']}")
|
||||
print(f" Missed: {stats['missed']}")
|
||||
print(f" Timeout: {stats['timeout']}")
|
||||
print(f" Unviable: {stats['unviable']}")
|
||||
|
||||
|
||||
def print_trend(history: list, limit: int = 10) -> None:
|
||||
"""Print mutation score trend."""
|
||||
if len(history) < 2:
|
||||
print("\n📊 Not enough history for trend analysis (need at least 2 runs)")
|
||||
return
|
||||
|
||||
print(f"\n📈 TREND (last {min(limit, len(history))} runs):")
|
||||
print("-" * 70)
|
||||
print(f"{'Date':<17} {'Branch':<15} {'Score':<8} {'Caught/Viable':<15} {'Change'}")
|
||||
print("-" * 70)
|
||||
|
||||
recent = history[-limit:] if len(history) > limit else history
|
||||
prev_score = None
|
||||
|
||||
for entry in recent:
|
||||
stats = entry['stats']
|
||||
score = stats['score']
|
||||
date = format_timestamp(entry['timestamp'])
|
||||
branch = entry['git_branch'][:14]
|
||||
caught_viable = f"{stats['caught']}/{stats['viable']}"
|
||||
|
||||
# Calculate change
|
||||
if prev_score is not None:
|
||||
change = score - prev_score
|
||||
if change > 0:
|
||||
change_str = f"+{change:.1f}% ⬆"
|
||||
elif change < 0:
|
||||
change_str = f"{change:.1f}% ⬇"
|
||||
else:
|
||||
change_str = "0.0% ➡"
|
||||
else:
|
||||
change_str = "-"
|
||||
|
||||
print(f"{date:<17} {branch:<15} {score:>6.1f}% {caught_viable:<15} {change_str}")
|
||||
prev_score = score
|
||||
|
||||
|
||||
def print_statistics(history: list) -> None:
|
||||
"""Print overall statistics."""
|
||||
if not history:
|
||||
return
|
||||
|
||||
scores = [entry['stats']['score'] for entry in history]
|
||||
avg_score = sum(scores) / len(scores)
|
||||
min_score = min(scores)
|
||||
max_score = max(scores)
|
||||
|
||||
print("\n📊 STATISTICS:")
|
||||
print("-" * 70)
|
||||
print(f"Total Runs: {len(history)}")
|
||||
print(f"Average Score: {avg_score:.2f}%")
|
||||
print(f"Min Score: {min_score:.2f}%")
|
||||
print(f"Max Score: {max_score:.2f}%")
|
||||
print(f"Score Range: {max_score - min_score:.2f}%")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
history_file = Path("mutation_history.json")
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
history_file = Path(sys.argv[1])
|
||||
|
||||
history = load_history(history_file)
|
||||
|
||||
print_summary(history)
|
||||
print_trend(history, limit=10)
|
||||
print_statistics(history)
|
||||
|
||||
print("\n" + "=" * 70 + "\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user