2026-08-17 14:34:22 +08:00
|
|
|
|
"""
|
|
|
|
|
|
build_plugin 打包命令冒烟测试
|
|
|
|
|
|
|
|
|
|
|
|
验证进程插件打包产物的完整性与错误路径:
|
|
|
|
|
|
1. demo 插件打包成功,zip 内含代码/配置/最小根配置/依赖清单/可执行 start.sh
|
|
|
|
|
|
2. 插件代码目录缺失时报 FileNotFoundError
|
|
|
|
|
|
3. 插件 config.toml 缺失时报 FileNotFoundError
|
|
|
|
|
|
|
|
|
|
|
|
运行: uv run python test_build_plugin.py
|
|
|
|
|
|
"""
|
|
|
|
|
|
import shutil
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import tempfile
|
|
|
|
|
|
import tomllib
|
|
|
|
|
|
import zipfile
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
SRC = Path(__file__).parent
|
2026-08-19 17:28:23 +08:00
|
|
|
|
sys.path.insert(0, str(SRC / "utils" / "cli" / "src"))
|
2026-08-17 14:34:22 +08:00
|
|
|
|
|
2026-08-19 17:28:23 +08:00
|
|
|
|
from chongming_cli import build_plugin as bp # noqa: E402
|
2026-08-17 14:34:22 +08:00
|
|
|
|
|
|
|
|
|
|
failures = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check(name, cond, detail=""):
|
|
|
|
|
|
status = "PASS" if cond else "FAIL"
|
|
|
|
|
|
print(f"[{status}] {name}" + (f" — {detail}" if detail else ""))
|
|
|
|
|
|
if not cond:
|
|
|
|
|
|
failures.append(name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tmp = Path(tempfile.mkdtemp(prefix="build_plugin_test_"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 1. demo 插件打包 ==========
|
|
|
|
|
|
zip_path = bp.build_plugin_package("demo", output_dir=tmp / "dist")
|
|
|
|
|
|
check("zip 产物生成", zip_path.exists(), str(zip_path))
|
|
|
|
|
|
|
|
|
|
|
|
with zipfile.ZipFile(zip_path) as z:
|
|
|
|
|
|
names = set(z.namelist())
|
|
|
|
|
|
check("包含插件代码 workers/demo/__init__.py", "workers/demo/__init__.py" in names)
|
|
|
|
|
|
check("包含插件配置 config/workers/demo/config.toml", "config/workers/demo/config.toml" in names)
|
|
|
|
|
|
check("包含 Zenoh 配置", "config/zenoh_config.json5" in names)
|
|
|
|
|
|
check("包含最小根配置 config.toml", "config.toml" in names)
|
|
|
|
|
|
check("包含依赖清单 pyproject.toml", "pyproject.toml" in names)
|
|
|
|
|
|
check("包含 start.sh", "start.sh" in names)
|
|
|
|
|
|
check("不包含 __pycache__", not any("__pycache__" in n for n in names))
|
|
|
|
|
|
|
|
|
|
|
|
# 最小根配置:仅 [zenoh] 节,不含网关侧敏感配置
|
|
|
|
|
|
root_conf = tomllib.loads(z.read("config.toml").decode())
|
|
|
|
|
|
check("根配置仅含 [zenoh] 节", set(root_conf.keys()) == {"zenoh"}, str(sorted(root_conf)))
|
|
|
|
|
|
check(
|
|
|
|
|
|
"根配置不泄露网关 [security] 密钥",
|
|
|
|
|
|
"secret_key" not in z.read("config.toml").decode(),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 依赖清单:chongming-worker 内网 git 源 + worker_serve 入口
|
|
|
|
|
|
pyproject = tomllib.loads(z.read("pyproject.toml").decode())
|
|
|
|
|
|
deps = pyproject["project"]["dependencies"]
|
|
|
|
|
|
check("pyproject 依赖包含 chongming-worker", "chongming-worker" in deps)
|
|
|
|
|
|
check("pyproject 依赖包含 orjson(SDK 运行时依赖)", "orjson" in deps)
|
|
|
|
|
|
check(
|
|
|
|
|
|
"pyproject 声明 worker_serve 入口",
|
|
|
|
|
|
pyproject["project"]["scripts"].get("worker_serve") == "chongming_worker.process:worker_serve",
|
|
|
|
|
|
)
|
|
|
|
|
|
check(
|
|
|
|
|
|
"pyproject 声明 chongming-worker git 源",
|
|
|
|
|
|
pyproject["tool"]["uv"]["sources"]["chongming-worker"]["git"] == bp.CHONGMING_WORKER_GIT,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# start.sh:可执行 + 指向正确的插件 type
|
|
|
|
|
|
start_sh = z.read("start.sh").decode()
|
|
|
|
|
|
check("start.sh 以 --type demo 启动", "--type demo" in start_sh)
|
|
|
|
|
|
perms = (z.getinfo("start.sh").external_attr >> 16) & 0o777
|
|
|
|
|
|
check("start.sh 可执行(0o755)", perms == 0o755, oct(perms))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 2. 错误路径:插件代码目录缺失 ==========
|
|
|
|
|
|
orig_workers = bp.WORKERS_DIR
|
|
|
|
|
|
bp.WORKERS_DIR = tmp / "no_workers"
|
|
|
|
|
|
try:
|
|
|
|
|
|
bp.build_plugin_package("ghost", output_dir=tmp / "dist")
|
|
|
|
|
|
check("缺插件代码目录应抛 FileNotFoundError", False)
|
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
|
check("缺插件代码目录应抛 FileNotFoundError", True)
|
|
|
|
|
|
finally:
|
|
|
|
|
|
bp.WORKERS_DIR = orig_workers
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 3. 错误路径:插件配置缺失 ==========
|
|
|
|
|
|
orig_conf_dir = bp.PLUGINS_CONFIG_DIR
|
|
|
|
|
|
bp.PLUGINS_CONFIG_DIR = tmp / "no_config"
|
|
|
|
|
|
try:
|
|
|
|
|
|
bp.build_plugin_package("demo", output_dir=tmp / "dist")
|
|
|
|
|
|
check("缺插件配置应抛 FileNotFoundError", False)
|
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
|
check("缺插件配置应抛 FileNotFoundError", True)
|
|
|
|
|
|
finally:
|
|
|
|
|
|
bp.PLUGINS_CONFIG_DIR = orig_conf_dir
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 4. 错误路径:插件名不合法 ==========
|
|
|
|
|
|
try:
|
|
|
|
|
|
bp.build_plugin_package("Bad-Name", output_dir=tmp / "dist")
|
|
|
|
|
|
check("非法插件名应抛 ValueError", False)
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
check("非法插件名应抛 ValueError", True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
|
|
|
|
|
|
|
|
|
|
print("\n" + ("=" * 40))
|
|
|
|
|
|
if failures:
|
|
|
|
|
|
print(f"❌ {len(failures)} 项未通过: {failures}")
|
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
print("✅ 全部通过")
|