Files
nanocode/skills/hand-drawn-diagram/tests/run_tests.py
T
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

204 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Regression test runner for hand-drawn-diagram skill.
Usage:
cd skills/hand-drawn-diagram
python3 tests/run_tests.py
Zero dependencies — uses only Python stdlib.
"""
import json
import os
import sys
import glob
import tempfile
# ---------------------------------------------------------------------------
# ANSI colors
# ---------------------------------------------------------------------------
GREEN = "\033[92m"
RED = "\033[91m"
YELLOW= "\033[93m"
BLUE = "\033[94m"
RESET = "\033[0m"
BOLD = "\033[1m"
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def ok(msg): print(f"{GREEN} ✓{RESET} {msg}")
def fail(msg): print(f"{RED} ✗{RESET} {msg}")
def info(msg): print(f"{BLUE} ℹ{RESET} {msg}")
def count_tag(content: str, tag: str) -> int:
"""Count occurrences of a tag in SVG/HTML content."""
return content.count(tag)
def run_assertion(content: str, assertion: dict, fixture_name: str) -> tuple[bool, str]:
"""Run a single assertion. Returns (passed, message)."""
check = assertion["check"]
if check == "not_empty":
if len(content.strip()) > 0:
return True, "output is not empty"
return False, "output is empty"
if check == "contains":
text = assertion.get("text", "")
if text in content:
return True, f"contains '{text[:40]}'"
return False, f"missing '{text[:40]}'"
if check == "contains_tag":
tag = assertion["tag"]
cnt = count_tag(content, tag)
min_count = assertion.get("min_count", 1)
if cnt >= min_count:
return True, f"contains {cnt}× '{tag}' (need ≥{min_count})"
return False, f"contains {cnt}× '{tag}' (need ≥{min_count})"
return False, f"unknown check: {check}"
# ---------------------------------------------------------------------------
# Main test runner
# ---------------------------------------------------------------------------
def main():
script_dir = os.path.dirname(os.path.abspath(__file__))
skill_dir = os.path.dirname(script_dir)
sys.path.insert(0, skill_dir)
from generator import (
quick_architecture,
DiagramGenerator,
Component, Connection, Container
)
fixture_dir = os.path.join(script_dir, "fixtures")
tmp_dir = tempfile.mkdtemp(prefix="hdg_test_")
fixture_files = sorted(glob.glob(os.path.join(fixture_dir, "*.json")))
if not fixture_files:
print(f"{YELLOW}No fixtures found in {fixture_dir}{RESET}")
sys.exit(1)
passed = 0
failed = 0
total_assertions = 0
failed_fixtures = []
print(f"\n{BOLD}Running {len(fixture_files)} fixture(s){RESET}")
print(f"Temp dir: {tmp_dir}\n")
for fp in fixture_files:
with open(fp, "r", encoding="utf-8") as f:
fixture = json.load(f)
name = fixture["name"]
desc = fixture.get("description", "")
ftype = fixture["type"]
params = fixture["params"]
assertions = fixture.get("assertions", [])
# Resolve output path
output = params["output"].replace("__TMP__", tmp_dir)
os.makedirs(os.path.dirname(output), exist_ok=True)
print(f"{BOLD}{name}{RESET} — {desc}")
# ---- generate ----
try:
if ftype == "quick_architecture":
quick_architecture(
params["title"],
params["layers"],
output,
style=params.get("style", "sketch"),
format=params.get("format", "svg")
)
elif ftype == "create_architecture":
gen = DiagramGenerator(style=params.get("style", "sketch"))
comps = [Component(**c) for c in params.get("components", [])]
conns = []
for c in params.get("connections", []):
# map 'from'/'to' to Connection field names
mapped = dict(c)
if "from" in mapped:
mapped["from_comp"] = mapped.pop("from")
if "to" in mapped:
mapped["to_comp"] = mapped.pop("to")
conns.append(Connection(**mapped))
conts = [Container(**c) for c in params.get("containers", [])]
gen.create_architecture(
params["title"],
comps,
conns,
output,
cards=params.get("cards", []),
auto_layout=params.get("auto_layout", False),
format=params.get("format", "svg"),
containers=conts
)
else:
fail(f"unknown fixture type: {ftype}")
failed += 1
failed_fixtures.append(name)
continue
except Exception as e:
fail(f"generation error: {e}")
failed += 1
failed_fixtures.append(name)
continue
# ---- read output ----
try:
with open(output, "r", encoding="utf-8") as f:
content = f.read()
except Exception as e:
fail(f"read error: {e}")
failed += 1
failed_fixtures.append(name)
continue
# ---- assertions ----
fixture_passed = True
for assertion in assertions:
total_assertions += 1
a_ok, msg = run_assertion(content, assertion, name)
if a_ok:
ok(msg)
else:
fail(msg)
fixture_passed = False
if fixture_passed:
passed += 1
else:
failed += 1
failed_fixtures.append(name)
print()
# ---- summary ----
print(f"{BOLD}{'='*50}{RESET}")
print(f"Fixtures: {len(fixture_files)}")
print(f"Passed: {GREEN}{passed}{RESET}")
print(f"Failed: {RED}{failed}{RESET}")
print(f"Assertions:{total_assertions}")
print(f"{BOLD}{'='*50}{RESET}")
if failed_fixtures:
print(f"\n{BOLD}Failed fixtures:{RESET}")
for fn in failed_fixtures:
print(f" {RED}• {fn}{RESET}")
# cleanup
import shutil
shutil.rmtree(tmp_dir, ignore_errors=True)
sys.exit(0 if failed == 0 else 1)
if __name__ == "__main__":
main()