Update json_to_md.py
Run Pytest with HTML and XML Test Reports / tests (push) Successful in 27s

This commit is contained in:
2025-07-18 02:00:02 +02:00
parent 12c5191287
commit f2af7f06a4
+137 -68
View File
@@ -8,17 +8,20 @@ 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)}"
else f"{space}- {stringify(e, indent + 1)}" # ← plus de \n !
for e in obj
)
if isinstance(obj, dict):
if not obj:
return "{}"
@@ -27,6 +30,7 @@ def stringify(obj, indent=0):
else f"{space}{k}:\n{stringify(v, indent + 1)}"
for k, v in obj.items()
)
return str(obj)
def sanitize_param_value(v):
@@ -35,92 +39,157 @@ def sanitize_param_value(v):
s.replace('&', '^')
.replace('<', '(')
.replace('>', ')')
.replace('"', '')
.replace("'", '')
.replace('`', '')
.replace('=', '')
.replace('\\', '/')
.replace('"', '')
.replace("'", '')
.replace('`', '')
.replace('=', '')
.replace('\\', '/')
)
def make_test_block(test, status, emoji, level, runtime_params):
def make_test_details(test, level, runtime_params):
nodeid = test.get("nodeid", "")
indent = " " * level
output = ""
summary_note = f"{emoji} Test #{test['global_number']}"
details = []
# Ajouter les paramètres d'exécution s'ils existent
callspec = runtime_params.get(nodeid, {})
params = callspec.get("params", {}) if isinstance(callspec, dict) else {}
param_display = ""
if isinstance(params, dict) and params:
param_display = ", ".join(f"{k}={sanitize_param_value(v)}" for k, v in params.items())
summary_note += f" (params: {param_display})"
output += f"{indent}- <details>\n"
output += f"{indent} <summary>{summary_note}</summary>\n\n"
# Runtime parameters block
if callspec:
callspec_block = "```python\n" + stringify(callspec) + "\n```"
output += f"{indent} - <details>\n"
output += f"{indent} <summary>📌 Runtime Parameters</summary>\n\n"
output += f"{callspec_block}\n\n"
output += f"{indent} </details>\n"
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:
output += f"{indent} ### 🔧 {phase.capitalize()} Phase\n"
phase_details = []
for field, value in test[phase].items():
details_body = "```python\n" + stringify(value) + "\n```" if value is not None else "None"
output += f"{indent} - <details>\n"
output += f"{indent} <summary>📌 {field.capitalize()}</summary>\n\n"
output += f"{details_body}\n\n"
output += f"{indent} </details>\n"
output += f"{indent}</details>\n"
return output
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(f"# 🧪 Test Report\n")
f.write("# 🧪 Rapport de tests\n\n")
local = pytz.timezone("Europe/Zurich")
now = datetime.now(local)
f.write(f"*Generated on {now.strftime('%Y-%m-%d %H:%M:%S %Z')}*\n\n")
f.write(f"*Généré le {now.strftime('%Y-%m-%d %H:%M:%S %Z')}*\n\n")
f.write("## 🔎 Tests\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
test_counter = 1
for test in data.get("tests", []):
test['global_number'] = test_counter
test_counter += 1
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")
grouped = defaultdict(lambda: defaultdict(list))
for test in data.get("tests", []):
nodeid = test.get("nodeid", "")
parts = nodeid.split("::")
filename = parts[0].replace("tests\\", "").replace("tests/", "")
funcname = parts[1].split("[")[0]
grouped[filename][funcname].append(test)
# 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")
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 tests:
test_block = make_test_block(test, test.get("outcome"), "➡️", level=2, runtime_params=runtime_params)
f.write(test_block + "\n")
f.write("</details>\n\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="Convert JSON test results to Markdown.")
parser.add_argument("--input", required=True, help="Path to pytest JSON file")
parser.add_argument("--output", required=True, help="Path to output Markdown file")
parser.add_argument("--params", required=False, help="Path to runtime-params.json")
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()
@@ -133,10 +202,10 @@ def main():
if "nodeid" in entry and isinstance(entry.get("callspec"), dict):
runtime_params[entry["nodeid"]] = entry["callspec"]
except Exception as e:
print(f"Failed to read runtime parameters: {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"✅ Report generated at {args.output}")
print(f"✅ Rapport généré à {args.output}")
if __name__ == "__main__":
main()
main()