correct test №1
This commit is contained in:
parent
4fb1fe532b
commit
23c0c61c5c
File diff suppressed because one or more lines are too long
BIN
testing/charts/report.png
Normal file
BIN
testing/charts/report.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 236 KiB |
@ -1,245 +0,0 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from collections import Counter, defaultdict
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.patches as mpatches
|
||||
|
||||
matplotlib.use("Agg")
|
||||
|
||||
RESULTS_FILE = Path(__file__).parent.parent / "results.json"
|
||||
CHARTS_DIR = Path(__file__).parent.parent / "charts"
|
||||
|
||||
STATUS_COLORS = {
|
||||
"passed": "#3ddc84",
|
||||
"failed": "#ff5c5c",
|
||||
"skipped": "#e0c84a",
|
||||
"error": "#ff8a8a",
|
||||
}
|
||||
|
||||
BG_COLOR = "#1a1a1a"
|
||||
PANEL_COLOR = "#242424"
|
||||
TEXT_COLOR = "#e0e0e0"
|
||||
GRID_COLOR = "#333333"
|
||||
FONT_MONO = "monospace"
|
||||
|
||||
|
||||
def _style_ax(ax, title: str):
|
||||
ax.set_facecolor(PANEL_COLOR)
|
||||
ax.set_title(title, color=TEXT_COLOR, fontsize=11, fontweight="bold", pad=10)
|
||||
ax.tick_params(colors=TEXT_COLOR, labelsize=8)
|
||||
ax.spines[:].set_color(GRID_COLOR)
|
||||
ax.yaxis.grid(True, color=GRID_COLOR, linewidth=0.5, linestyle="--")
|
||||
ax.set_axisbelow(True)
|
||||
for label in ax.get_xticklabels() + ax.get_yticklabels():
|
||||
label.set_color(TEXT_COLOR)
|
||||
label.set_fontfamily(FONT_MONO)
|
||||
|
||||
|
||||
def load_data() -> tuple[pd.DataFrame, dict]:
|
||||
if not RESULTS_FILE.exists():
|
||||
raise FileNotFoundError(f"results.json not found at {RESULTS_FILE}")
|
||||
|
||||
raw = json.loads(RESULTS_FILE.read_text(encoding="utf-8"))
|
||||
tests = raw.get("tests", [])
|
||||
|
||||
rows = []
|
||||
for t in tests:
|
||||
node = t["nodeid"]
|
||||
parts = node.split("::")
|
||||
module = parts[0].replace("tests/", "").replace("/", ".").replace(".py", "")
|
||||
cls = parts[1] if len(parts) >= 3 else "unknown"
|
||||
name = parts[-1]
|
||||
|
||||
duration = t.get("call", {}).get("duration", 0.0) if t.get("call") else 0.0
|
||||
|
||||
rows.append({
|
||||
"module": module,
|
||||
"class": cls,
|
||||
"name": name,
|
||||
"outcome": t["outcome"],
|
||||
"duration": duration,
|
||||
})
|
||||
|
||||
df = pd.DataFrame(rows)
|
||||
summary = raw.get("summary", {})
|
||||
return df, summary
|
||||
|
||||
|
||||
def chart_overall_status(df: pd.DataFrame, ax: plt.Axes):
|
||||
counts = df["outcome"].value_counts()
|
||||
colors = [STATUS_COLORS.get(k, "#888") for k in counts.index]
|
||||
|
||||
wedges, texts, pcts = ax.pie(
|
||||
counts.values,
|
||||
labels=counts.index,
|
||||
colors=colors,
|
||||
autopct="%1.1f%%",
|
||||
startangle=90,
|
||||
pctdistance=0.78,
|
||||
wedgeprops={"edgecolor": BG_COLOR, "linewidth": 2},
|
||||
)
|
||||
for t in texts:
|
||||
t.set_color(TEXT_COLOR)
|
||||
t.set_fontsize(9)
|
||||
t.set_fontfamily(FONT_MONO)
|
||||
for p in pcts:
|
||||
p.set_color("#111")
|
||||
p.set_fontsize(8)
|
||||
p.set_fontweight("bold")
|
||||
|
||||
ax.set_facecolor(PANEL_COLOR)
|
||||
ax.set_title("Overall Results", color=TEXT_COLOR, fontsize=11, fontweight="bold", pad=10)
|
||||
|
||||
|
||||
def chart_by_module(df: pd.DataFrame, ax: plt.Axes):
|
||||
pivot = (
|
||||
df.groupby(["module", "outcome"])
|
||||
.size()
|
||||
.unstack(fill_value=0)
|
||||
.reindex(columns=["passed", "failed", "skipped", "error"], fill_value=0)
|
||||
)
|
||||
|
||||
x = np.arange(len(pivot))
|
||||
width = 0.2
|
||||
offset = -(len(pivot.columns) - 1) / 2 * width
|
||||
|
||||
for i, col in enumerate(pivot.columns):
|
||||
bars = ax.bar(
|
||||
x + offset + i * width,
|
||||
pivot[col],
|
||||
width,
|
||||
label=col,
|
||||
color=STATUS_COLORS.get(col, "#888"),
|
||||
edgecolor=BG_COLOR,
|
||||
linewidth=0.8,
|
||||
)
|
||||
|
||||
ax.set_xticks(x)
|
||||
ax.set_xticklabels(pivot.index, rotation=25, ha="right", fontsize=7, fontfamily=FONT_MONO)
|
||||
ax.set_ylabel("Tests", color=TEXT_COLOR, fontsize=9)
|
||||
ax.legend(
|
||||
fontsize=7,
|
||||
labelcolor=TEXT_COLOR,
|
||||
facecolor=PANEL_COLOR,
|
||||
edgecolor=GRID_COLOR,
|
||||
)
|
||||
_style_ax(ax, "Results by Module")
|
||||
|
||||
|
||||
def chart_duration_histogram(df: pd.DataFrame, ax: plt.Axes):
|
||||
durations = df.loc[df["outcome"] != "skipped", "duration"].values * 1000
|
||||
|
||||
if len(durations) == 0:
|
||||
ax.text(0.5, 0.5, "No data", ha="center", va="center", color=TEXT_COLOR)
|
||||
_style_ax(ax, "Test Duration (ms)")
|
||||
return
|
||||
|
||||
mean_ms = float(np.mean(durations))
|
||||
median_ms = float(np.median(durations))
|
||||
p95_ms = float(np.percentile(durations, 95))
|
||||
|
||||
ax.hist(durations, bins=20, color="#5cb8ff", edgecolor=BG_COLOR, linewidth=0.6, alpha=0.85)
|
||||
ax.axvline(mean_ms, color="#3ddc84", linewidth=1.5, linestyle="--", label=f"Mean {mean_ms:.1f} ms")
|
||||
ax.axvline(median_ms, color="#e0c84a", linewidth=1.5, linestyle=":", label=f"Median {median_ms:.1f} ms")
|
||||
ax.axvline(p95_ms, color="#ff5c5c", linewidth=1.5, linestyle="-.", label=f"P95 {p95_ms:.1f} ms")
|
||||
|
||||
ax.set_xlabel("ms", color=TEXT_COLOR, fontsize=9)
|
||||
ax.set_ylabel("Tests", color=TEXT_COLOR, fontsize=9)
|
||||
ax.legend(fontsize=7, labelcolor=TEXT_COLOR, facecolor=PANEL_COLOR, edgecolor=GRID_COLOR)
|
||||
_style_ax(ax, "Test Duration (ms)")
|
||||
|
||||
|
||||
def chart_slowest_tests(df: pd.DataFrame, ax: plt.Axes):
|
||||
top = (
|
||||
df[df["outcome"] != "skipped"]
|
||||
.nlargest(10, "duration")
|
||||
.copy()
|
||||
)
|
||||
top["label"] = top["class"] + "::" + top["name"]
|
||||
top["duration"] = top["duration"] * 1000
|
||||
|
||||
colors = [STATUS_COLORS.get(o, "#888") for o in top["outcome"]]
|
||||
bars = ax.barh(top["label"], top["duration"], color=colors, edgecolor=BG_COLOR, linewidth=0.6)
|
||||
|
||||
ax.set_xlabel("ms", color=TEXT_COLOR, fontsize=9)
|
||||
ax.tick_params(axis="y", labelsize=7)
|
||||
ax.invert_yaxis()
|
||||
_style_ax(ax, "Top 10 Slowest Tests")
|
||||
|
||||
|
||||
def chart_stats_table(df: pd.DataFrame, summary: dict, ax: plt.Axes):
|
||||
ax.set_facecolor(PANEL_COLOR)
|
||||
ax.axis("off")
|
||||
|
||||
total = len(df)
|
||||
passed = summary.get("passed", 0)
|
||||
failed = summary.get("failed", 0)
|
||||
skipped = summary.get("skipped", 0)
|
||||
duration = df["duration"].sum() * 1000
|
||||
|
||||
durations = df.loc[df["outcome"] != "skipped", "duration"].values * 1000
|
||||
|
||||
rows = [
|
||||
["Total tests", str(total)],
|
||||
["Passed", str(passed)],
|
||||
["Failed", str(failed)],
|
||||
["Skipped", str(skipped)],
|
||||
["Pass rate", f"{passed / total * 100:.1f}%" if total else "—"],
|
||||
["Total time", f"{duration:.0f} ms"],
|
||||
["Mean duration", f"{np.mean(durations):.1f} ms" if len(durations) else "—"],
|
||||
["Median", f"{np.median(durations):.1f} ms" if len(durations) else "—"],
|
||||
["P95", f"{np.percentile(durations, 95):.1f} ms" if len(durations) else "—"],
|
||||
]
|
||||
|
||||
table = ax.table(
|
||||
cellText=rows,
|
||||
colLabels=["Metric", "Value"],
|
||||
cellLoc="left",
|
||||
loc="center",
|
||||
colWidths=[0.6, 0.4],
|
||||
)
|
||||
table.auto_set_font_size(False)
|
||||
table.set_fontsize(9)
|
||||
|
||||
for (row, col), cell in table.get_celld().items():
|
||||
cell.set_facecolor("#2e2e2e" if row % 2 == 0 else PANEL_COLOR)
|
||||
cell.set_edgecolor(GRID_COLOR)
|
||||
cell.set_text_props(color=TEXT_COLOR, fontfamily=FONT_MONO)
|
||||
|
||||
ax.set_title("Summary", color=TEXT_COLOR, fontsize=11, fontweight="bold", pad=10)
|
||||
|
||||
|
||||
def generate(output_path: Path = None) -> Path:
|
||||
CHARTS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
output_path = output_path or CHARTS_DIR / "report.png"
|
||||
|
||||
df, summary = load_data()
|
||||
|
||||
fig = plt.figure(figsize=(18, 14), facecolor=BG_COLOR)
|
||||
fig.suptitle(
|
||||
"Legal AI Assistant — Test Report",
|
||||
fontsize=16, fontweight="bold", color=TEXT_COLOR,
|
||||
y=0.98, fontfamily=FONT_MONO,
|
||||
)
|
||||
|
||||
gs = fig.add_gridspec(2, 3, hspace=0.45, wspace=0.35,
|
||||
top=0.93, bottom=0.05, left=0.06, right=0.97)
|
||||
|
||||
chart_overall_status(df, fig.add_subplot(gs[0, 0]))
|
||||
chart_by_module(df, fig.add_subplot(gs[0, 1:]))
|
||||
chart_duration_histogram(df, fig.add_subplot(gs[1, 0]))
|
||||
chart_slowest_tests(df, fig.add_subplot(gs[1, 1]))
|
||||
chart_stats_table(df, summary, fig.add_subplot(gs[1, 2]))
|
||||
|
||||
fig.savefig(output_path, dpi=150, bbox_inches="tight", facecolor=BG_COLOR)
|
||||
plt.close(fig)
|
||||
return output_path
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
out = generate()
|
||||
print(f"Chart saved: {out}")
|
||||
File diff suppressed because one or more lines are too long
@ -14,12 +14,8 @@ def run_pytest() -> int:
|
||||
|
||||
args = [
|
||||
sys.executable, "-m", "pytest",
|
||||
|
||||
str(TESTS_DIR / "tests"),
|
||||
|
||||
"--json-report",
|
||||
f"--json-report-file={RESULTS_JSON}",
|
||||
|
||||
f"--html={REPORT_HTML}",
|
||||
"--self-contained-html",
|
||||
|
||||
@ -29,32 +25,18 @@ def run_pytest() -> int:
|
||||
f"--cov-report=html:{CHARTS_DIR / 'coverage'}",
|
||||
|
||||
"--tb=no",
|
||||
"-q",
|
||||
]
|
||||
|
||||
return subprocess.run(args, cwd=str(ROOT), check=False).returncode
|
||||
|
||||
|
||||
def run_charts():
|
||||
from testing.reports.charts import generate
|
||||
return generate()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Start testing")
|
||||
|
||||
code = run_pytest()
|
||||
|
||||
charts_path = None
|
||||
|
||||
if RESULTS_JSON.exists():
|
||||
charts_path = run_charts()
|
||||
|
||||
print()
|
||||
print(f"pytest-html: {REPORT_HTML}")
|
||||
print(f"\npytest-html: {REPORT_HTML}")
|
||||
print(f"pytest-cov: {CHARTS_DIR / 'coverage' / 'index.html'}")
|
||||
print(f"charts: {charts_path or CHARTS_DIR / 'report.png'}")
|
||||
|
||||
print()
|
||||
print("Stop testing")
|
||||
print("\nStop testing")
|
||||
|
||||
sys.exit(code)
|
||||
Loading…
Reference in New Issue
Block a user