modify 修复前端展示配置与实际插件配置不一致的bug
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
"""
|
||||
[workers.config] 两种配置模式(toml / cache)语义单测 —— 无需网关与缓存后端。
|
||||
|
||||
用 dict 作缓存桩,验证:
|
||||
- cache 模式:以缓存为准(插件初始化前解析即读到缓存值),缓存缺失才用 toml 引导;
|
||||
- toml 模式:以 config.toml 为准,启动时覆盖缓存,使前端(读缓存)与运行值一致;
|
||||
- count:toml 模式用文件值覆盖缓存,cache 模式缓存有值用缓存、缺失用文件值引导;
|
||||
- get_config 的来源标注(值来自缓存 vs 回退读 toml 种子)如实区分。
|
||||
|
||||
对应改动:chongming_worker.runtime.resolve_runtime_config / resolve_startup_config /
|
||||
_read_worker_config_section_with_source(Plugin.create_workers 在 on_init 前调用)。
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../utils/worker/src"))
|
||||
|
||||
import chongming_worker.runtime as r
|
||||
|
||||
PASS = 0
|
||||
FAIL = 0
|
||||
|
||||
WT = "demo"
|
||||
TOML_CFG = {"api_key": "TOML-KEY", "model": "toml-model", "heartbeat": {"interval": 5}}
|
||||
|
||||
|
||||
def report(name, cond, detail=""):
|
||||
global PASS, FAIL
|
||||
if cond:
|
||||
PASS += 1
|
||||
print(f"✅ {name}" + (f" — {detail}" if detail else ""))
|
||||
else:
|
||||
FAIL += 1
|
||||
print(f"❌ {name}" + (f" — {detail}" if detail else ""))
|
||||
|
||||
|
||||
def write_plugin_toml(root: str) -> None:
|
||||
"""在工作目录下写 config/workers/<type>/config.toml(_plugin_config_path 相对 cwd)。"""
|
||||
d = os.path.join(root, "config", "workers", WT)
|
||||
os.makedirs(d, exist_ok=True)
|
||||
with open(os.path.join(d, "config.toml"), "w", encoding="utf-8") as f:
|
||||
f.write(
|
||||
"[workers]\n"
|
||||
f'type = "{WT}"\n'
|
||||
'module = "plugins.demo"\n'
|
||||
"count = 3\n"
|
||||
"\n"
|
||||
"[workers.config]\n"
|
||||
'api_key = "TOML-KEY"\n'
|
||||
'model = "toml-model"\n'
|
||||
"\n"
|
||||
"[workers.config.heartbeat]\n"
|
||||
"interval = 5\n"
|
||||
)
|
||||
|
||||
|
||||
def run() -> int:
|
||||
workdir = tempfile.mkdtemp(prefix="cm_cfg_mode_")
|
||||
cwd = os.getcwd()
|
||||
try:
|
||||
write_plugin_toml(workdir)
|
||||
os.chdir(workdir)
|
||||
|
||||
key = r._configs_cache_key(WT)
|
||||
# _discover_plugins 展平出来的形态:注册字段 + [workers.config] 各键平铺到顶层
|
||||
base = {
|
||||
"type": WT, "count": 3,
|
||||
"api_key": "TOML-KEY", "model": "toml-model",
|
||||
"heartbeat": {"interval": 5},
|
||||
}
|
||||
cached_cfg = {"api_key": "CACHE-KEY", "model": "cache-model"}
|
||||
|
||||
# ── 1. cache 模式:以缓存为准(插件必须读到缓存值)──────────────
|
||||
cache = {key: dict(cached_cfg)}
|
||||
got = r.resolve_runtime_config(WT, base, cache, "cache")
|
||||
report(
|
||||
"cache 模式:返回值取缓存值(覆盖 toml 同键)",
|
||||
got.get("api_key") == "CACHE-KEY" and got.get("model") == "cache-model",
|
||||
f"api_key={got.get('api_key')}",
|
||||
)
|
||||
report(
|
||||
"cache 模式:toml 独有键保留(缓存缺该键时兜底)",
|
||||
got.get("heartbeat") == {"interval": 5},
|
||||
f"heartbeat={got.get('heartbeat')}",
|
||||
)
|
||||
report(
|
||||
"cache 模式:不覆盖既有缓存值",
|
||||
cache[key] == cached_cfg,
|
||||
f"cache={cache[key]}",
|
||||
)
|
||||
|
||||
# 插件初始化路径(process.py 先 set_worker_cache/set_worker_mode 再 create_workers)
|
||||
r.set_worker_cache(WT, cache)
|
||||
r.set_worker_mode(WT, "cache")
|
||||
startup = r.resolve_startup_config(WT, base, worker_manager=None)
|
||||
report(
|
||||
"cache 模式:resolve_startup_config 让插件初始化读到缓存值(本次核心修复)",
|
||||
startup.get("api_key") == "CACHE-KEY",
|
||||
f"api_key={startup.get('api_key')}",
|
||||
)
|
||||
vs_front = r._read_worker_config_section_with_source(cache, WT)
|
||||
report(
|
||||
"cache 模式:插件初始化值与前端( get_config )读到的一致",
|
||||
vs_front[0].get("api_key") == startup.get("api_key") == "CACHE-KEY"
|
||||
and vs_front[1] == "cache",
|
||||
f"source={vs_front[1]}",
|
||||
)
|
||||
|
||||
# ── 2. cache 模式:缓存缺失 → 用 toml 引导并写回 ────────────────
|
||||
cache2 = {}
|
||||
got2 = r.resolve_runtime_config(WT, base, cache2, "cache")
|
||||
report(
|
||||
"cache 模式:缓存缺失用 toml 引导并写回",
|
||||
got2.get("api_key") == "TOML-KEY" and cache2.get(key, {}).get("api_key") == "TOML-KEY",
|
||||
f"cache={cache2.get(key)}",
|
||||
)
|
||||
|
||||
# ── 3. toml 模式:以文件为准,启动时覆盖缓存 ────────────────────
|
||||
cache3 = {key: dict(cached_cfg)}
|
||||
got3 = r.resolve_runtime_config(WT, base, cache3, "toml")
|
||||
report(
|
||||
"toml 模式:返回值取 toml 值",
|
||||
got3.get("api_key") == "TOML-KEY" and got3.get("model") == "toml-model",
|
||||
f"api_key={got3.get('api_key')}",
|
||||
)
|
||||
report(
|
||||
"toml 模式:启动时用 toml 覆盖缓存(前端随之显示 toml 值)",
|
||||
cache3.get(key, {}).get("api_key") == "TOML-KEY",
|
||||
f"cache={cache3.get(key)}",
|
||||
)
|
||||
r.set_worker_cache(WT, cache3)
|
||||
r.set_worker_mode(WT, "toml")
|
||||
startup3 = r.resolve_startup_config(WT, base, worker_manager=None)
|
||||
report(
|
||||
"toml 模式:插件初始化值与缓存/前端一致(均为 toml 值)",
|
||||
startup3.get("api_key") == "TOML-KEY"
|
||||
and r._read_worker_config_section(cache3, WT).get("api_key") == "TOML-KEY",
|
||||
f"api_key={startup3.get('api_key')}",
|
||||
)
|
||||
|
||||
# ── 4. 无缓存:保持 toml 种子 ────────────────────────────────
|
||||
got4 = r.resolve_runtime_config(WT, base, None, "toml")
|
||||
report(
|
||||
"无缓存后端:保持 toml 种子(经典行为)",
|
||||
got4.get("api_key") == "TOML-KEY",
|
||||
f"api_key={got4.get('api_key')}",
|
||||
)
|
||||
|
||||
# ── 5. get_config 来源标注 ──────────────────────────────────
|
||||
only_missing_key = {"worker:cm:configs:other": {"api_key": "X"}}
|
||||
cfg5, src5 = r._read_worker_config_section_with_source(only_missing_key, WT)
|
||||
report(
|
||||
"来源标注:缓存对象存在但键缺失 → 回退 toml 且标 toml",
|
||||
src5 == "toml" and cfg5.get("api_key") == "TOML-KEY",
|
||||
f"source={src5}",
|
||||
)
|
||||
cfg6, src6 = r._read_worker_config_section_with_source({key: {"api_key": "C"}}, WT)
|
||||
report("来源标注:命中缓存 → cache", src6 == "cache" and cfg6.get("api_key") == "C", f"source={src6}")
|
||||
|
||||
# ── 6. count:toml 模式覆盖 / cache 模式以缓存为准 ──────────────
|
||||
counts_key = r._SCALE_COUNTS_KEY.format(type=WT)
|
||||
cache7 = {counts_key: 1}
|
||||
n_toml = r.resolve_worker_count(WT, 3, cache7, "toml")
|
||||
report(
|
||||
"count toml 模式:用 toml 的 count 覆盖缓存",
|
||||
n_toml == 3 and cache7.get(counts_key) == 3,
|
||||
f"got={n_toml}, cache={cache7.get(counts_key)}",
|
||||
)
|
||||
cache8 = {counts_key: 1}
|
||||
n_cache = r.resolve_worker_count(WT, 3, cache8, "cache")
|
||||
report(
|
||||
"count cache 模式:缓存有值用缓存(跨重启保持)",
|
||||
n_cache == 1 and cache8.get(counts_key) == 1,
|
||||
f"got={n_cache}",
|
||||
)
|
||||
cache9 = {}
|
||||
n_seed = r.resolve_worker_count(WT, 3, cache9, "cache")
|
||||
report(
|
||||
"count cache 模式:缓存缺失用 toml 引导并写回",
|
||||
n_seed == 3 and cache9.get(counts_key) == 3,
|
||||
f"got={n_seed}, cache={cache9.get(counts_key)}",
|
||||
)
|
||||
|
||||
# ── 7. Plugin.create_workers:初始化(on_init)前即应用模式语义 ──────
|
||||
# 这是本次主 bug:cache 模式下插件此前按 toml 建客户端,只有 RPC/管理通道读缓存。
|
||||
import asyncio
|
||||
|
||||
from chongming_worker.plugin import Plugin
|
||||
|
||||
seen = {}
|
||||
|
||||
class DemoPlugin(Plugin):
|
||||
type = WT
|
||||
auto_bind = False
|
||||
|
||||
async def _bootstrap(self): # 隔离数据库/对象存储引导,只验证配置来源
|
||||
return None
|
||||
|
||||
async def on_init(self):
|
||||
seen["on_init"] = self.config.get("api_key")
|
||||
|
||||
def _create_workers_impl(self, session, config, count=1, worker_manager=None):
|
||||
seen["impl"] = (config or {}).get("api_key")
|
||||
return []
|
||||
|
||||
r.set_worker_cache(WT, {key: {"api_key": "CACHE-KEY"}})
|
||||
r.set_worker_mode(WT, "cache")
|
||||
asyncio.run(DemoPlugin().create_workers(None, dict(base), 1))
|
||||
report(
|
||||
"Plugin.create_workers:cache 模式 on_init 读到缓存值",
|
||||
seen.get("on_init") == "CACHE-KEY",
|
||||
f"on_init={seen.get('on_init')}",
|
||||
)
|
||||
report(
|
||||
"Plugin.create_workers:下发给 WorkerPool 的配置同为缓存值",
|
||||
seen.get("impl") == "CACHE-KEY",
|
||||
f"impl={seen.get('impl')}",
|
||||
)
|
||||
|
||||
r.set_worker_cache(WT, {key: {"api_key": "CACHE-KEY"}})
|
||||
r.set_worker_mode(WT, "toml")
|
||||
seen.clear()
|
||||
asyncio.run(DemoPlugin().create_workers(None, dict(base), 1))
|
||||
report(
|
||||
"Plugin.create_workers:toml 模式 on_init 读到文件值",
|
||||
seen.get("on_init") == "TOML-KEY" and seen.get("impl") == "TOML-KEY",
|
||||
f"on_init={seen.get('on_init')}, impl={seen.get('impl')}",
|
||||
)
|
||||
|
||||
print("\n" + "=" * 56)
|
||||
print(f"测试结果: ✅ {PASS} 通过 ❌ {FAIL} 失败")
|
||||
return 0 if FAIL == 0 else 1
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
shutil.rmtree(workdir, ignore_errors=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run())
|
||||
Reference in New Issue
Block a user