Files
slic/json_to_md.py
T
tligui_y 06eb44acbd
Run Pytest with HTML and XML Test Reports / tests (push) Successful in 26s
Update json_to_md.py
2025-07-18 02:36:43 +02:00

178 lines
6.6 KiB
Python

from collections import defaultdict
from datetime import datetime
import json
import argparse
import os
import re
import pytz
import traceback
def stringify(obj, indent=0):
"""Convertit un objet en chaîne formatée avec indentation"""
space = ' ' * indent
if obj is None or obj == "":
return "None"
if isinstance(obj, list):
if not obj:
return "[]"
return '\n'.join(f"{space}- {stringify(e, indent + 1)}" for e in obj)
if isinstance(obj, dict):
if not obj:
return "{}"
return '\n'.join(f"{space}{k}: {stringify(v, indent + 1)}" for k, v in obj.items())
return str(obj)
def sanitize_param_value(v):
"""Nettoie les valeurs pour le Markdown"""
return str(v).translate(str.maketrans({
'&': '^', '<': '(', '>': ')', '"': '', "'": '',
'`': '', '=': '≡', '\\': '/'
}))
def generate_test_details(test, runtime_params):
"""Génère les détails d'un test"""
details = []
nodeid = test.get("nodeid", "")
# Paramètres
callspec = runtime_params.get(nodeid, {})
if callspec:
params = callspec.get("params", {})
if params:
details.append("```python")
details.extend(f"{k} = {sanitize_param_value(v)}" for k, v in params.items())
details.append("```")
# Phases
for phase in ["setup", "call", "teardown"]:
if phase in test and test[phase]:
phase_info = test[phase]
details.append(f"**Phase {phase}**:")
details.append("```python")
for k, v in phase_info.items():
if v is not None:
details.append(f"{k}: {stringify(v)}")
details.append("```")
return "\n".join(details)
def json_to_md_nested(json_path, md_path, runtime_params=None):
"""Génère le rapport Markdown complet"""
runtime_params = runtime_params or {}
with open(json_path) as f:
data = json.load(f)
with open(md_path, 'w', encoding='utf-8') as f:
# En-tête
f.write("# 🧪 Rapport de tests\n\n")
f.write(f"*Généré le {datetime.now(pytz.timezone('Europe/Paris')).strftime('%Y-%m-%d %H:%M:%S')}*\n\n")
# Section Résumé
if 'summary' in data:
f.write("## 📋 Résumé\n")
for k, v in data['summary'].items():
f.write(f"- **{k}**: {v}\n")
f.write("\n")
# Section Tests
if "tests" in data and data["tests"]:
f.write("## 🔎 Tests\n\n")
# Numérotation et groupement
test_counter = 1
file_groups = defaultdict(list)
for test in data["tests"]:
test['num'] = test_counter
test_counter += 1
filename = test["nodeid"].split("::")[0].replace("tests/", "").replace("tests\\", "")
file_groups[filename].append(test)
# Par fichier
for filename, tests in file_groups.items():
f.write(f"- <details>\n")
f.write(f" <summary>📁 {filename}</summary>\n\n")
# Par fonction
func_groups = defaultdict(list)
for test in tests:
funcname = test["nodeid"].split("::")[1].split("[")[0]
func_groups[funcname].append(test)
for funcname, func_tests in func_groups.items():
f.write(f" - 🔧 {funcname}\n\n")
# Tests individuels
for test in func_tests:
status = "✅" if test['outcome'] == 'passed' else "❌"
f.write(f" - <details>\n")
f.write(f" <summary>➡️ Test #{test['num']} {status}</summary>\n\n")
details = generate_test_details(test, runtime_params)
if details:
f.write(f"{details}\n")
else:
f.write("Aucun détail disponible\n")
f.write(f" </details>\n\n")
f.write(f" </details>\n\n")
# Section Collecteurs
if "collectors" in data and data["collectors"]:
f.write("## 📚 Collecteurs\n\n")
for collector in data["collectors"]:
outcome = collector.get("outcome", "unknown")
emoji = "✅" if outcome == "passed" else "❌"
f.write(f"- {emoji} {collector.get('nodeid', 'unknown')}\n")
if outcome != "passed" and "result" in collector:
f.write(" ```\n")
for item in collector["result"]:
if isinstance(item, dict):
for k, v in item.items():
f.write(f" {k}: {v}\n")
else:
f.write(f" {item}\n")
f.write(" ```\n")
f.write("\n")
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--input", required=True, help="Fichier JSON d'entrée")
parser.add_argument("--output", required=True, help="Fichier Markdown de sortie")
parser.add_argument("--log", help="Fichier log pytest")
parser.add_argument("--params", help="Fichier de paramètres runtime")
parser.add_argument("--exit-code", type=int, default=0, help="Code de sortie pytest")
args = parser.parse_args()
# Chargement des paramètres
runtime_params = {}
if args.params and os.path.exists(args.params):
try:
with open(args.params) as f:
for entry in json.load(f):
if "nodeid" in entry and "callspec" in entry:
runtime_params[entry["nodeid"]] = entry["callspec"]
except Exception as e:
print(f"⚠️ Erreur lecture paramètres: {e}")
try:
json_to_md_nested(args.input, args.output, runtime_params)
# Ajout des logs si erreur
if args.log and os.path.exists(args.log) and args.exit_code != 0:
with open(args.log) as lf:
logs = lf.read()
with open(args.output, 'r+') as f:
content = f.read()
f.seek(0, 0)
f.write(f"## ⚠️ Logs d'exécution\n```\n{logs}\n```\n\n{content}")
print(f"✅ Rapport généré: {args.output}")
except Exception as e:
print(f"❌ Erreur: {str(e)}")
traceback.print_exc()
if __name__ == "__main__":
main()