voiceforge / backend /tests /quality /coverage_tracker.py
lordofgaming
Initial VoiceForge deployment (clean)
673435a
"""
VoiceForge - Coverage & Function Tracker
-----------------------------------------
Tracks test coverage and identifies untested functions:
- Collects all public functions in codebase
- Matches against existing tests
- Generates coverage report
"""
import ast
import sys
from pathlib import Path
from collections import defaultdict
def collect_functions(root_dir: Path) -> dict[str, list[str]]:
"""Collect all public functions from Python files"""
functions = defaultdict(list)
for py_file in root_dir.rglob("*.py"):
if '__pycache__' in str(py_file) or 'test_' in py_file.name:
continue
try:
with open(py_file, 'r', encoding='utf-8', errors='ignore') as f:
source = f.read()
tree = ast.parse(source)
module_name = py_file.stem
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
# Skip private functions
if not node.name.startswith('_'):
functions[module_name].append(node.name)
except Exception:
pass
return functions
def collect_tested_functions(test_dir: Path) -> set[str]:
"""Extract function names that are being tested"""
tested = set()
for test_file in test_dir.rglob("test_*.py"):
try:
with open(test_file, 'r', encoding='utf-8', errors='ignore') as f:
source = f.read()
tree = ast.parse(source)
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
# Extract tested function name from test name
test_name = node.name
if test_name.startswith('test_'):
# e.g., test_transcribe_audio -> transcribe_audio
func_name = test_name[5:]
tested.add(func_name)
# Also check for mocked functions
for child in ast.walk(node):
if isinstance(child, ast.Attribute):
tested.add(child.attr)
except Exception:
pass
return tested
def run_coverage_analysis(app_dir: str = "app", test_dir: str = "tests"):
"""Run coverage analysis and report untested functions"""
print("=" * 60)
print("📊 VoiceForge Function Coverage Tracker")
print("=" * 60)
app_path = Path(app_dir)
test_path = Path(test_dir)
if not app_path.exists():
print(f"❌ App directory not found: {app_dir}")
sys.exit(1)
# Collect all functions
all_functions = collect_functions(app_path)
total_functions = sum(len(funcs) for funcs in all_functions.values())
# Collect tested functions
tested_functions = collect_tested_functions(test_path)
print(f"\n📁 Scanned: {len(all_functions)} modules, {total_functions} functions")
print(f"🧪 Tests cover: {len(tested_functions)} function patterns\n")
# Find untested
untested = defaultdict(list)
tested_count = 0
for module, funcs in all_functions.items():
for func in funcs:
if func in tested_functions or any(func in t for t in tested_functions):
tested_count += 1
else:
untested[module].append(func)
coverage = (tested_count / total_functions * 100) if total_functions > 0 else 0
print("📈 COVERAGE SUMMARY")
print("-" * 40)
print(f" Total Functions: {total_functions}")
print(f" Tested: {tested_count}")
print(f" Untested: {total_functions - tested_count}")
print(f" Coverage: {coverage:.1f}%")
# Coverage bar
bar_length = int(coverage / 5)
bar = "█" * bar_length + "░" * (20 - bar_length)
print(f"\n [{bar}] {coverage:.1f}%")
# Untested by module
print("\n⚠️ UNTESTED FUNCTIONS (by module)")
print("-" * 40)
for module, funcs in sorted(untested.items())[:10]:
print(f"\n 📦 {module}:")
for func in funcs[:5]:
print(f" • {func}()")
if len(funcs) > 5:
print(f" ... and {len(funcs) - 5} more")
print("\n" + "=" * 60)
if coverage >= 70:
print("✅ Coverage: GOOD")
return 0
elif coverage >= 40:
print("⚠️ Coverage: NEEDS IMPROVEMENT")
return 1
else:
print("❌ Coverage: LOW")
return 2
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Track VoiceForge function coverage")
parser.add_argument("--app", default="app", help="App source directory")
parser.add_argument("--tests", default="tests", help="Tests directory")
args = parser.parse_args()
sys.exit(run_coverage_analysis(args.app, args.tests))