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

211 lines
8.1 KiB
Python

from collections import defaultdict
from datetime import datetime
import json
import argparse
import os
import re
import pytz
def stringify(obj, indent=0):
space = ' ' * indent
if obj is None or obj == "":
return "None"
if isinstance(obj, list):
if not obj:
return "[]"
return '\n'.join(
f"{space}- (vide)" if not e or (isinstance(e, dict) and not e)
else f"{space}- {stringify(e, indent + 1)}" if not isinstance(e, dict)
else f"{space}- {stringify(e, indent + 1)}" # ← plus de \n !
for e in obj
)
if isinstance(obj, dict):
if not obj:
return "{}"
return '\n'.join(
f"{space}{k}: {stringify(v, indent + 1)}" if not isinstance(v, (dict, list))
else f"{space}{k}:\n{stringify(v, indent + 1)}"
for k, v in obj.items()
)
return str(obj)
def sanitize_param_value(v):
s = str(v)
return (
s.replace('&', '^')
.replace('<', '(')
.replace('>', ')')
.replace('"', '')
.replace("'", '')
.replace('`', '')
.replace('=', '≡')
.replace('\\', '/')
)
def make_test_details(test, level, runtime_params):
nodeid = test.get("nodeid", "")
details = []
# Ajouter les paramètres d'exécution s'ils existent
callspec = runtime_params.get(nodeid, {})
if callspec:
params_str = "```python\n" + stringify(callspec) + "\n```"
details.append(f" - **Paramètres**:\n{params_str}")
# Ajouter les phases (setup, call, teardown)
skip_keys = {"nodeid"}
phase_keys = [k for k in test.keys() if isinstance(test[k], dict) and k not in skip_keys]
for phase in phase_keys:
phase_details = []
for field, value in test[phase].items():
if value is not None:
phase_details.append(f" - **{field}**:\n ```python\n{stringify(value)}\n ```")
if phase_details:
details.append(f" - **Phase {phase}**:\n" + "\n".join(phase_details))
return "\n".join(details)
def json_to_md_nested(json_path, md_path, runtime_params=None):
with open(json_path) as f:
data = json.load(f)
with open(md_path, 'w', encoding='utf-8') as f:
f.write("# 🧪 Rapport de tests\n\n")
local = pytz.timezone("Europe/Zurich")
now = datetime.now(local)
f.write(f"*Généré le {now.strftime('%Y-%m-%d %H:%M:%S %Z')}*\n\n")
# Section Informations générales
general_info = {}
for k, v in data.items():
if k == "summary":
break
if k not in {"created", "exitcode"}:
general_info[k] = v
if general_info:
f.write("## 🧾 Informations générales\n")
for key, value in general_info.items():
f.write(f"- **{key}**: {stringify(value)}\n")
f.write("\n")
# Section Résumé
if 'summary' in data:
f.write("## 📋 Résumé\n")
for key, value in data['summary'].items():
f.write(f"- **{key.capitalize()}**: {stringify(value)}\n")
f.write("\n")
# Section Tests
if "tests" in data and "summary" in data:
test_counter = 1
for test in data["tests"]:
test['global_number'] = test_counter
test_counter += 1
f.write("## 🔎 Tests\n")
# Grouper les tests par statut
tests_by_status = defaultdict(list)
for test in data["tests"]:
outcome = test.get("outcome", "unknown")
tests_by_status[outcome].append(test)
# Grouper les tests par fichier et fonction
grouped = defaultdict(lambda: defaultdict(list))
for test in data["tests"]:
nodeid = test.get("nodeid", "")
parts = nodeid.split("::")
filename = parts[0].replace("tests\\", "").replace("tests/", "")
funcname = parts[1].split("[")[0] if len(parts) > 1 else "unknown"
grouped[filename][funcname].append(test)
# Générer le rapport par fichier
for filename, funcs in grouped.items():
f.write(f"- <details>\n <summary>📁 {filename}</summary>\n\n")
for funcname, tests in funcs.items():
f.write(f" - 🔧 {funcname}\n\n")
for test in sorted(tests, key=lambda x: x['global_number']):
outcome = test.get("outcome", "unknown")
emoji = "✅" if outcome == "passed" else "❌"
duration = test.get("duration", 0)
# Header du test
f.write(f" - <details>\n <summary>{emoji} Test #{test['global_number']}</summary>\n\n")
# Corps du test
test_details = make_test_details(test, level=3, runtime_params=runtime_params)
if test_details:
f.write(test_details + "\n")
f.write(" </details>\n\n")
f.write("</details>\n\n")
# Section Collecteurs
if "collectors" in data:
f.write("## 📚 Fichiers collectés\n")
grouped = defaultdict(list)
for collector in data["collectors"]:
nodeid = collector.get("nodeid", "unknown")
path = nodeid.split("::")[0]
main_folder = path.split("/")[0] if "/" in path else path
grouped[main_folder].append(collector)
for folder, collectors in grouped.items():
has_fail = any(c.get("outcome") != "passed" for c in collectors)
folder_emoji = "✅" if not has_fail else "❌"
f.write(f"- <details>\n <summary>{folder_emoji} {folder}</summary>\n\n")
for collector in sorted(collectors, key=lambda c: c.get("nodeid", "").split("[")[0]):
outcome = collector.get("outcome", "unknown")
emoji = "✅" if outcome == "passed" else "❌"
nodeid = collector.get("nodeid", "unknown")
short_node = nodeid.split("[")[0]
f.write(f" - <details>\n <summary>{emoji} {short_node}</summary>\n\n")
f.write(f" - **Statut**: `{outcome}`\n")
other_keys = {k: v for k, v in collector.items() if k not in {"nodeid", "outcome"}}
if other_keys:
for k, v in other_keys.items():
f.write(f" - **{k}**:\n ```python\n{stringify(v)}\n ```\n")
f.write(" </details>\n\n")
f.write("</details>\n\n")
def main():
parser = argparse.ArgumentParser(description="Convertir les résultats de tests JSON en Markdown.")
parser.add_argument("--input", required=True, help="Chemin vers le fichier JSON pytest")
parser.add_argument("--output", required=True, help="Chemin vers le fichier Markdown de sortie")
parser.add_argument("--params", required=False, help="Chemin vers runtime-params.json")
args = parser.parse_args()
runtime_params = {}
if args.params and os.path.exists(args.params):
try:
with open(args.params, "r") as f:
param_list = json.load(f)
for entry in param_list:
if "nodeid" in entry and isinstance(entry.get("callspec"), dict):
runtime_params[entry["nodeid"]] = entry["callspec"]
except Exception as e:
print(f"❌ Échec de lecture des paramètres d'exécution: {e}")
json_to_md_nested(args.input, args.output, runtime_params)
print(f"✅ Rapport généré à {args.output}")
if __name__ == "__main__":
main()