Files
zhuangang/tools/run_examples.py
T

97 lines
4.3 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
"""运行 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)
- Java:*.java 用 `java 文件名.java` 单文件运行(需要 JDK 21+;可用环境变量 JAVA 指定 java 可执行文件)
- 文件开头写 `# 不自动运行` / `// 不自动运行` 的跳过(需要外部服务的示例)
- 运行时设置环境变量 `RUN_EXAMPLES=1`:会长期运行的服务(如 MCP stdio 服务)据此只做导入检查、不启动
用法: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), "RUN_EXAMPLES": "1"}, stdin=subprocess.DEVNULL)
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", ".java") 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)
elif f.suffix == ".java":
java = os.environ.get("JAVA") or shutil.which("java")
if not java:
results.append(("SKIP", frel, "没有安装 JDK"))
continue
ok, out = run([java, f.name], 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())