#!/usr/bin/env python3 """运行 topics/**/examples/ 下的示例代码(抽查“代码能跑”)。 - Go:含 go.mod 的目录执行 `go vet ./... && go test ./...` - Python:*.py 直接运行(文件开头可写 `# requires: 包1, 包2`,缺包时跳过);test_*.py 用 pytest 运行 - TypeScript:*.ts 用 Node.js 直接运行(需要 Node 23.6+ 的类型剥离,推荐 Node 24 LTS) - 文件开头写 `# 不自动运行` / `// 不自动运行` 的跳过(需要外部服务的示例) 用法:python3 tools/run_examples.py [路径过滤关键词] """ from __future__ import annotations import importlib.util import os import re import shutil import subprocess import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] def run(cmd: list[str] | str, cwd: Path, timeout: int = 180) -> tuple[bool, str]: try: p = subprocess.run(cmd, cwd=cwd, shell=isinstance(cmd, str), capture_output=True, text=True, timeout=timeout, env={**os.environ, "PYTHONPATH": str(cwd)}) return p.returncode == 0, (p.stdout + p.stderr)[-1500:] except subprocess.TimeoutExpired: return False, "超时" def node_ok() -> bool: if not shutil.which("node"): return False v = subprocess.run(["node", "-v"], capture_output=True, text=True).stdout.strip().lstrip("v").split(".") return (int(v[0]), int(v[1])) >= (23, 6) def main() -> int: flt = sys.argv[1] if len(sys.argv) > 1 else "" results: list[tuple[str, str, str]] = [] has_node = node_ok() for ex in sorted(ROOT.glob("topics/**/examples/*")): rel = ex.relative_to(ROOT).as_posix() if flt and flt not in rel: continue if ex.is_dir() and (ex / "go.mod").exists(): if not shutil.which("go"): results.append(("SKIP", rel, "没有安装 Go")) continue ok, out = run("go vet ./... && go test ./...", ex) results.append(("PASS" if ok else "FAIL", rel, "" if ok else out)) continue targets = [ex] if ex.is_file() else sorted(ex.rglob("*")) for f in targets: frel = f.relative_to(ROOT).as_posix() if f.suffix not in (".py", ".ts") or "node_modules" in f.parts: continue head = f.read_text(encoding="utf-8")[:400] if "不自动运行" in head: results.append(("SKIP", frel, "标记为不自动运行")) continue if f.suffix == ".py": m = re.search(r"^# requires:\s*(.+)$", head, re.M) missing = [p.strip() for p in m.group(1).split(",")] if m else [] missing = [p for p in missing if importlib.util.find_spec(p) is None] if missing: results.append(("SKIP", frel, f"缺少依赖 {missing}")) continue cmd = [sys.executable, "-m", "pytest", "-q", f.name] if f.name.startswith("test_") \ else [sys.executable, f.name] ok, out = run(cmd, f.parent) else: if not has_node: results.append(("SKIP", frel, "需要 Node 23.6+")) continue ok, out = run(["node", f.name], f.parent) results.append(("PASS" if ok else "FAIL", frel, "" if ok else out)) for status, rel, out in results: print(f"{status:4} {rel}" + (f"\n{out}" if out and status == "FAIL" else f" ({out})" if out else "")) n = {s: sum(r[0] == s for r in results) for s in ("PASS", "FAIL", "SKIP")} print(f"\n通过 {n['PASS']},失败 {n['FAIL']},跳过 {n['SKIP']}") return 1 if n["FAIL"] else 0 if __name__ == "__main__": sys.exit(main())