soleprint init commit
This commit is contained in:
182
station/tools/tester/index.py
Normal file
182
station/tools/tester/index.py
Normal file
@@ -0,0 +1,182 @@
|
||||
"""
|
||||
Test index generator - creates browsable view of available tests.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
import ast
|
||||
|
||||
|
||||
def parse_test_file(file_path: Path) -> Dict:
|
||||
"""Parse a test file and extract test methods with docstrings."""
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
tree = ast.parse(f.read())
|
||||
|
||||
module_doc = ast.get_docstring(tree)
|
||||
classes = []
|
||||
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.ClassDef):
|
||||
class_doc = ast.get_docstring(node)
|
||||
methods = []
|
||||
|
||||
for item in node.body:
|
||||
if isinstance(item, ast.FunctionDef) and item.name.startswith('test_'):
|
||||
method_doc = ast.get_docstring(item)
|
||||
methods.append({
|
||||
'name': item.name,
|
||||
'doc': method_doc or "No description"
|
||||
})
|
||||
|
||||
if methods: # Only include classes with test methods
|
||||
classes.append({
|
||||
'name': node.name,
|
||||
'doc': class_doc or "No description",
|
||||
'methods': methods
|
||||
})
|
||||
|
||||
return {
|
||||
'file': file_path.name,
|
||||
'module_doc': module_doc or "No module description",
|
||||
'classes': classes
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
'file': file_path.name,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
|
||||
def build_test_index(tests_dir: Path) -> Dict:
|
||||
"""
|
||||
Build a hierarchical index of all tests.
|
||||
|
||||
Returns structure:
|
||||
{
|
||||
'mascotas': {
|
||||
'test_pet_owners.py': {...},
|
||||
'test_pets.py': {...}
|
||||
},
|
||||
'productos': {...},
|
||||
...
|
||||
}
|
||||
"""
|
||||
index = {}
|
||||
|
||||
# Find all domain directories (mascotas, productos, etc.)
|
||||
for domain_dir in tests_dir.iterdir():
|
||||
if not domain_dir.is_dir():
|
||||
continue
|
||||
if domain_dir.name.startswith('_'):
|
||||
continue
|
||||
|
||||
domain_tests = {}
|
||||
|
||||
# Find all test_*.py files in domain
|
||||
for test_file in domain_dir.glob('test_*.py'):
|
||||
test_info = parse_test_file(test_file)
|
||||
domain_tests[test_file.name] = test_info
|
||||
|
||||
if domain_tests: # Only include domains with tests
|
||||
index[domain_dir.name] = domain_tests
|
||||
|
||||
return index
|
||||
|
||||
|
||||
def generate_markdown_index(index: Dict) -> str:
|
||||
"""Generate markdown representation of test index."""
|
||||
lines = ["# Contract Tests Index\n"]
|
||||
|
||||
for domain, files in sorted(index.items()):
|
||||
lines.append(f"## {domain.capitalize()}\n")
|
||||
|
||||
for filename, file_info in sorted(files.items()):
|
||||
if 'error' in file_info:
|
||||
lines.append(f"### {filename} ⚠️ Parse Error")
|
||||
lines.append(f"```\n{file_info['error']}\n```\n")
|
||||
continue
|
||||
|
||||
lines.append(f"### {filename}")
|
||||
lines.append(f"{file_info['module_doc']}\n")
|
||||
|
||||
for cls in file_info['classes']:
|
||||
lines.append(f"#### {cls['name']}")
|
||||
lines.append(f"*{cls['doc']}*\n")
|
||||
|
||||
for method in cls['methods']:
|
||||
# Extract first line of docstring
|
||||
first_line = method['doc'].split('\n')[0].strip()
|
||||
lines.append(f"- `{method['name']}` - {first_line}")
|
||||
|
||||
lines.append("")
|
||||
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_html_index(index: Dict) -> str:
|
||||
"""Generate HTML representation of test index."""
|
||||
html = ['<!DOCTYPE html><html><head>']
|
||||
html.append('<meta charset="utf-8">')
|
||||
html.append('<title>Contract Tests Index</title>')
|
||||
html.append('<style>')
|
||||
html.append('''
|
||||
body { font-family: system-ui, -apple-system, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
|
||||
h1 { color: #2c3e50; border-bottom: 3px solid #3498db; padding-bottom: 10px; }
|
||||
h2 { color: #34495e; margin-top: 40px; border-bottom: 2px solid #95a5a6; padding-bottom: 8px; }
|
||||
h3 { color: #7f8c8d; margin-top: 30px; }
|
||||
h4 { color: #95a5a6; margin-top: 20px; margin-bottom: 10px; }
|
||||
.module-doc { font-style: italic; color: #7f8c8d; margin-bottom: 15px; }
|
||||
.class-doc { font-style: italic; color: #95a5a6; margin-bottom: 10px; }
|
||||
.test-method { margin-left: 20px; padding: 8px; background: #ecf0f1; margin-bottom: 5px; border-radius: 4px; }
|
||||
.test-name { font-family: monospace; color: #2980b9; font-weight: bold; }
|
||||
.test-doc { color: #34495e; margin-left: 10px; }
|
||||
.error { background: #e74c3c; color: white; padding: 10px; border-radius: 4px; }
|
||||
.domain-badge { display: inline-block; background: #3498db; color: white; padding: 3px 10px; border-radius: 12px; font-size: 12px; margin-left: 10px; }
|
||||
''')
|
||||
html.append('</style></head><body>')
|
||||
|
||||
html.append('<h1>Contract Tests Index</h1>')
|
||||
html.append(f'<p>Total domains: {len(index)}</p>')
|
||||
|
||||
for domain, files in sorted(index.items()):
|
||||
test_count = sum(len(f.get('classes', [])) for f in files.values())
|
||||
html.append(f'<h2>{domain.capitalize()} <span class="domain-badge">{test_count} test classes</span></h2>')
|
||||
|
||||
for filename, file_info in sorted(files.items()):
|
||||
if 'error' in file_info:
|
||||
html.append(f'<h3>{filename} ⚠️</h3>')
|
||||
html.append(f'<div class="error">Parse Error: {file_info["error"]}</div>')
|
||||
continue
|
||||
|
||||
html.append(f'<h3>{filename}</h3>')
|
||||
html.append(f'<div class="module-doc">{file_info["module_doc"]}</div>')
|
||||
|
||||
for cls in file_info['classes']:
|
||||
html.append(f'<h4>{cls["name"]}</h4>')
|
||||
html.append(f'<div class="class-doc">{cls["doc"]}</div>')
|
||||
|
||||
for method in cls['methods']:
|
||||
first_line = method['doc'].split('\n')[0].strip()
|
||||
html.append(f'<div class="test-method">')
|
||||
html.append(f'<span class="test-name">{method["name"]}</span>')
|
||||
html.append(f'<span class="test-doc">{first_line}</span>')
|
||||
html.append('</div>')
|
||||
|
||||
html.append('</body></html>')
|
||||
return '\n'.join(html)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# CLI usage
|
||||
import sys
|
||||
|
||||
tests_dir = Path(__file__).parent / 'tests'
|
||||
index = build_test_index(tests_dir)
|
||||
|
||||
if '--html' in sys.argv:
|
||||
print(generate_html_index(index))
|
||||
else:
|
||||
print(generate_markdown_index(index))
|
||||
Reference in New Issue
Block a user