Spaces:
Sleeping
Sleeping
File size: 5,113 Bytes
673435a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | """
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))
|