modify:进程插件开发环境目录结构变更

This commit is contained in:
2026-08-25 17:03:35 +08:00
parent d8cb69bb6b
commit 741d7cda09
5 changed files with 188 additions and 104 deletions
+61 -19
View File
@@ -2,9 +2,10 @@
build_plugin 打包命令冒烟测试
验证进程插件打包产物的完整性与错误路径:
1. demo 插件打包成功,zip 内含代码/配置/最小根配置/依赖清单/可执行 start.sh
2. 插件代码目录缺失时报 FileNotFoundError
3. 插件 config.toml 缺失时报 FileNotFoundError
1. demo 插件打包成功,zip = 项目根直接压缩(代码/配置/最小根配置/依赖清单/可执行 start.sh)
2. 打包排除 __pycache__ / .venv / *.pyc 等非产物内容
3. 插件项目目录缺失时报 FileNotFoundError
4. 插件项目内配置缺失时报 FileNotFoundError
运行: uv run python test_build_plugin.py
"""
@@ -16,9 +17,10 @@ import zipfile
from pathlib import Path
SRC = Path(__file__).parent
sys.path.insert(0, str(SRC / "utils" / "cli" / "src"))
sys.path.insert(0, str(SRC.parent / "utils" / "cli" / "src"))
from chongming_cli import build_plugin as bp # noqa: E402
from chongming_cli import gen_plugin as gp # noqa: E402
failures = []
@@ -47,9 +49,9 @@ with zipfile.ZipFile(zip_path) as z:
check("包含 start.sh", "start.sh" in names)
check("不包含 __pycache__", not any("__pycache__" in n for n in names))
# 最小根配置:仅 [zenoh] 节,不含网关侧敏感配置
# 最小根配置:worker 启动需 [zenoh] 节,且不含网关侧敏感配置
root_conf = tomllib.loads(z.read("config.toml").decode())
check("根配置仅含 [zenoh] 节", set(root_conf.keys()) == {"zenoh"}, str(sorted(root_conf)))
check("根配置含 [zenoh] 节", "zenoh" in root_conf, str(sorted(root_conf)))
check(
"根配置不泄露网关 [security] 密钥",
"secret_key" not in z.read("config.toml").decode(),
@@ -66,7 +68,7 @@ with zipfile.ZipFile(zip_path) as z:
)
check(
"pyproject 声明 chongming-worker git 源",
pyproject["tool"]["uv"]["sources"]["chongming-worker"]["git"] == bp.CHONGMING_WORKER_GIT,
pyproject["tool"]["uv"]["sources"]["chongming-worker"]["git"] == gp.CHONGMING_WORKER_GIT,
)
# start.sh:可执行 + 指向正确的插件 type
@@ -76,31 +78,71 @@ with zipfile.ZipFile(zip_path) as z:
check("start.sh 可执行(0o755)", perms == 0o755, oct(perms))
# ========== 2. 错误路径:插件代码目录缺失 ==========
# ========== 2. 打包排除非产物内容(临时最小项目) ==========
def make_mini_project(proj: Path) -> None:
"""构造一个结构合法的迷你插件项目(含需要排除的垃圾文件)。"""
(proj / "workers" / "mini" / "router").mkdir(parents=True)
(proj / "workers" / "mini" / "__init__.py").write_text("", encoding="utf-8")
(proj / "workers" / "mini" / "router" / "__init__.py").write_text("", encoding="utf-8")
conf = proj / "config" / "workers" / "mini"
conf.mkdir(parents=True)
(conf / "config.toml").write_text(
'[workers]\ntype = "mini"\nmodule = "workers.mini"\nload_mode = "external"\n',
encoding="utf-8",
)
(proj / "config" / "zenoh_config.json5").write_text("{}", encoding="utf-8")
(proj / "config.toml").write_text('[zenoh]\njson5_path = "config/zenoh_config.json5"\n', encoding="utf-8")
(proj / "pyproject.toml").write_text("[project]\nname = \"mini-worker\"\n", encoding="utf-8")
(proj / "start.sh").write_text("#!/usr/bin/env bash\necho mini\n", encoding="utf-8")
(proj / "start.sh").chmod(0o755)
# 垃圾内容:缓存目录、字节码、虚拟环境
(proj / "workers" / "mini" / "__pycache__" / "x.pyc").parent.mkdir(parents=True)
(proj / "workers" / "mini" / "__pycache__" / "x.pyc").write_bytes(b"x")
(proj / "workers" / "mini" / "y.pyc").write_bytes(b"y")
(proj / ".venv" / "bin" / "pip").parent.mkdir(parents=True)
(proj / ".venv" / "bin" / "pip").write_text("", encoding="utf-8")
orig_workers = bp.WORKERS_DIR
bp.WORKERS_DIR = tmp / "no_workers"
mini_proj = tmp / "workers" / "mini"
make_mini_project(mini_proj)
bp.WORKERS_DIR = tmp / "workers"
try:
bp.build_plugin_package("ghost", output_dir=tmp / "dist")
check("缺插件代码目录应抛 FileNotFoundError", False)
except FileNotFoundError:
check("缺插件代码目录应抛 FileNotFoundError", True)
zip_path = bp.build_plugin_package("mini", output_dir=tmp / "dist")
with zipfile.ZipFile(zip_path) as z:
names = set(z.namelist())
check("排除 __pycache__", not any("__pycache__" in n for n in names), str(sorted(names)))
check("排除 *.pyc", not any(n.endswith(".pyc") for n in names))
check("排除 .venv", not any(n.startswith(".venv") for n in names))
check("保留代码与配置", {"workers/mini/__init__.py", "config/workers/mini/config.toml"} <= names)
finally:
bp.WORKERS_DIR = orig_workers
# ========== 3. 错误路径:插件配置缺失 ==========
orig_conf_dir = bp.PLUGINS_CONFIG_DIR
bp.PLUGINS_CONFIG_DIR = tmp / "no_config"
# ========== 3. 错误路径:插件项目目录缺失 ==========
bp.WORKERS_DIR = tmp / "no_workers"
try:
bp.build_plugin_package("demo", output_dir=tmp / "dist")
bp.build_plugin_package("ghost", output_dir=tmp / "dist")
check("缺插件项目目录应抛 FileNotFoundError", False)
except FileNotFoundError:
check("缺插件项目目录应抛 FileNotFoundError", True)
finally:
bp.WORKERS_DIR = orig_workers
# ========== 4. 错误路径:项目内插件配置缺失 ==========
# 只建代码目录、不建 config/workers/<name>/config.toml
(orig_workers / "ghost").mkdir(parents=True)
try:
bp.build_plugin_package("ghost", output_dir=tmp / "dist")
check("缺插件配置应抛 FileNotFoundError", False)
except FileNotFoundError:
check("缺插件配置应抛 FileNotFoundError", True)
finally:
bp.PLUGINS_CONFIG_DIR = orig_conf_dir
shutil.rmtree(orig_workers / "ghost", ignore_errors=True)
# ========== 4. 错误路径:插件名不合法 ==========
# ========== 5. 错误路径:插件名不合法 ==========
try:
bp.build_plugin_package("Bad-Name", output_dir=tmp / "dist")
check("非法插件名应抛 ValueError", False)
+17 -11
View File
@@ -1,12 +1,12 @@
"""
混合模式(external 插件)端到端测试
进程插件(external 模式)端到端测试
前置条件:
1. 网关正常运行(uv run serve);example 插件配置为 external:
config/workers/example/config.toml [workers] 节 load_mode = "external"
2. 本脚本自己拉起/停止独立 worker 进程(uv run worker_serve --type example)
1. 网关正常运行(uv run serve)
2. demo 进程插件(workers/demo/,load_mode = "external")
验证:
本脚本从 demo 插件项目根(= 打包 zip 根)拉起/停止独立 worker 进程
(worker_serve --type demo),验证:
1. worker 启动后 → HTTP 路由动态出现且跨进程 RPC 可调
2. worker 优雅退出 → 路由动态摘除(404)
3. worker 重启 → 路由恢复
@@ -18,13 +18,17 @@ import urllib.request
from pathlib import Path
GATEWAY_URL = "http://localhost:8000"
TEST_URL = f"{GATEWAY_URL}/api/v1/calc/add?a=3&b=4"
TEST_URL = f"{GATEWAY_URL}/api/v1/demo/hello?name=chongming"
POLL_TIMEOUT = 30.0
# 优先使用 venv 控制台脚本(uv sync 后生成),不存在时退回 uv run
_WORKER_CMD = [str(Path(sys.executable).parent / "worker_serve"), "--type", "example"]
# 插件项目根(= 容器工作目录 = zip 根):worker 在项目根下读配置/import 代码
REPO = Path(__file__).parent.parent
PROJECT = REPO / "workers" / "demo"
# 优先使用仓库 venv 控制台脚本(避免在插件项目内新建 venv),否则退回 uv run --project
_WORKER_CMD = [str(Path(sys.executable).parent / "worker_serve"), "--type", "demo"]
if not Path(_WORKER_CMD[0]).exists():
_WORKER_CMD = ["uv", "run", "worker_serve", "--type", "example"]
_WORKER_CMD = ["uv", "run", "--project", str(REPO), "worker_serve", "--type", "demo"]
PASS = 0
FAIL = 0
@@ -63,9 +67,10 @@ def wait_http(target_status=200, timeout=POLL_TIMEOUT):
def run():
# 1. 拉起独立 worker 进程
# 1. 拉起独立 worker 进程(CWD = 插件项目根,与容器一致)
proc = subprocess.Popen(
_WORKER_CMD,
cwd=str(PROJECT),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
@@ -83,6 +88,7 @@ def run():
# 3. 重启 worker → 路由恢复
proc2 = subprocess.Popen(
_WORKER_CMD,
cwd=str(PROJECT),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
@@ -103,5 +109,5 @@ def run():
if __name__ == "__main__":
print("提示: 请先以 load_mode=process 启动网关 (uv run serve)\n")
print(f"提示: 请先启动网关 (uv run serve);worker 将从 {PROJECT} 拉起\n")
sys.exit(run())
+10
View File
@@ -4,12 +4,22 @@ Worker 进程隔离性测试
验证进程插件与网关完全隔离:阻断 chongming(网关包)的两种导入路径后,
worker 侧全链路(SDK 导入 → 插件配置自读 → 插件 import → 契约校验)仍可用。
与容器行为一致:chdir 到插件项目根 workers/demo/(= 打包 zip 根),
worker_serve 即以该目录为 CWD 读配置、并把 CWD 加入 sys.path。
任何对 chongming.* / src.chongming.* 的导入都会直接触发 ImportError。
"""
import os
import sys
from pathlib import Path
SRC = Path(__file__).parent
REPO = SRC.parent
# 模拟容器:CWD = 插件项目根(zip 根),并把它置于 sys.path 首位
PROJECT = REPO / "workers" / "demo"
os.chdir(PROJECT)
sys.path.insert(0, str(PROJECT))
sys.path.insert(0, str(SRC))
# 阻断网关包导入(两种访问路径)。chongming_worker / zenoh / pydantic 等不受影响。