Files
oc_session/_test_registry.py
T

224 lines
10 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.
# -*- coding: utf-8 -*-
"""skill.json 注册表单测:内存加载 / 落库 / 内嵌常见根 / 分层查找 / 后台扫盘。
覆盖 2026-09-25 需求:推拉遇软链接空壳 -> 先查内存注册表 -> 再快扫内嵌常见
终端位置(oc/wb/cc-switch…)-> 都没中才后台扫盘(不阻塞),扫完落库 skill.json
(名字 -> 位置 的一堆键值);用户选定的有效目录永远优先(real_src 返回 None)。
"""
import json
import os
import sys
import tempfile
import time
from pathlib import Path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import ocskill # noqa: E402
FAIL: list[str] = []
def check(name: str, cond: bool, extra: str = "") -> None:
print(("PASS " if cond else "FAIL ") + name + (" | " + extra if extra else ""))
if not cond:
FAIL.append(name)
tmp = Path(tempfile.mkdtemp(prefix="ocskill_reg_")).resolve()
print("tmp =", tmp)
_orig = { # 测完还原,别污染同进程后续用例
"REGISTRY_PATH": ocskill.REGISTRY_PATH,
"_REG": ocskill._REG,
"env_alt": os.environ.get("OC_SKILL_ALT_ROOTS"),
"builtin": ocskill.builtin_skill_roots,
"deep": ocskill.deep_scan_roots,
"bg": ocskill.start_background_scan,
}
def reset_registry(path: Path) -> None:
ocskill.REGISTRY_PATH = path
ocskill._REG = None # 强制下次 load_registry 重读
def mk_skill(folder: Path, name: str) -> Path:
folder.mkdir(parents=True, exist_ok=True)
(folder / "SKILL.md").write_text(
f"---\nname: {name}\ndescription: 测试技能\n---\n# {name}\n",
encoding="utf-8")
return folder
try:
# ---------- 1. 空表起步 + remember + 落库 + 启动重读入内存 ----------
reset_registry(tmp / "skill.json")
reg = ocskill.load_registry()
check("1 空表起步 skills={}", reg.get("skills") == {}, str(reg))
real_a = mk_skill(tmp / "roots" / "A" / "foo-skill", "foo-skill")
ocskill.remember_skills({"foo-skill": [str(real_a)]})
check("1 remember 进内存", ocskill.registry_locations("foo-skill") == [str(real_a)],
str(ocskill.registry_locations("foo-skill")))
check("1 remember 落库文件存在", (tmp / "skill.json").is_file())
data = json.loads((tmp / "skill.json").read_text(encoding="utf-8"))
check("1 落库结构 = version/updated/skills",
{"version", "updated", "skills"} <= set(data.keys())
and data["skills"].get("foo-skill") == [str(real_a)], str(data)[:200])
ocskill._REG = None # 模拟下次启动
reg2 = ocskill.load_registry()
check("1 启动重读入内存(skill.json -> 内存)",
reg2.get("skills", {}).get("foo-skill") == [str(real_a)], str(reg2)[:200])
# 重复 remember 不产生重复条目
ocskill.remember_skills({"foo-skill": [str(real_a)]})
check("1 remember 幂等(不重复)",
ocskill.registry_locations("foo-skill") == [str(real_a)])
# 损坏文件不炸:从空表起步
(tmp / "skill.json").write_text("{broken json", encoding="utf-8")
ocskill._REG = None
reg3 = ocskill.load_registry()
check("1 损坏 skill.json -> 空表起步不炸", reg3.get("skills") == {}, str(reg3))
# ---------- 2. 内嵌常见终端根 ----------
roots = ocskill.builtin_skill_roots()
home = Path.home()
for tag, expect in [
("oc", home / ".config" / "opencode" / "skills"),
("wb", home / ".workbuddy" / "skills"),
("cc-switch", home / ".cc-switch" / "skills"),
("claude", home / ".claude" / "skills"),
]:
key = os.path.normcase(os.path.abspath(str(expect)))
check(f"2 内嵌 {tag} 位置", key in [os.path.normcase(os.path.abspath(r)) for r in roots],
str(expect))
agent_dirs = [os.path.normcase(os.path.abspath(t["dir"]))
for t in ocskill.load_agent_table() if t.get("dir")]
got = [os.path.normcase(os.path.abspath(r)) for r in roots]
check("2 agents.md 终端目录纳入", all(d in got for d in agent_dirs),
f"agents={agent_dirs}")
check("2 内嵌根无重复", len(got) == len(set(got)), str(len(got)))
check("2 env 追加根", True) # 用例 3 实际验证
# ---------- 3. tier1 快扫 scan_roots_for_name + env 追加 ----------
root_b = tmp / "roots" / "B"
target = mk_skill(root_b / "quick-skill", "quick-skill")
found = ocskill.scan_roots_for_name("quick-skill", roots=[str(root_b)])
check("3 快扫按名字命中真身", found == [str(target)], str(found))
check("3 快扫找不到时返回空", ocskill.scan_roots_for_name("nope-xyz", roots=[str(root_b)]) == [])
# env 追加的根也进 builtin
os.environ["OC_SKILL_ALT_ROOTS"] = str(root_b)
roots_env = ocskill.builtin_skill_roots()
check("2 env 追加根进 builtin",
os.path.normcase(os.path.abspath(str(root_b))) in
[os.path.normcase(os.path.abspath(r)) for r in roots_env], str(roots_env)[:200])
# ---------- 4. real_src 分层:① 内存注册表命中(零扫盘) ----------
reset_registry(tmp / "skill2.json")
ocskill.remember_skills({"foo-skill": [str(real_a)]})
shell1 = tmp / "shell" / "foo-skill"
shell1.mkdir(parents=True) # 空壳:目录在、没有 SKILL.md
bg_called: list[str] = []
ocskill.start_background_scan = lambda name="": bg_called.append(name) or True
real = ocskill.real_src(str(shell1))
check("4 空壳 -> 内存注册表一步命中", real == str(real_a), f"real={real}")
check("4 注册表命中不触发后台扫盘", bg_called == [], str(bg_called))
# ---------- 5. real_src 分层:② tier1 快扫命中并落库 ----------
shell2 = tmp / "shell" / "quick-skill"
shell2.mkdir(parents=True)
ocskill._REG = None # 空注册表,逼走 tier1
real2 = ocskill.real_src(str(shell2))
check("5 空壳 -> 快扫内嵌常见位置命中", real2 == str(target), f"real={real2}")
check("5 快扫命中即落库",
"quick-skill" in ocskill.load_registry().get("skills", {}),
str(ocskill.load_registry().get("skills", {}))[:200])
check("5 快扫命中不触发后台扫盘", bg_called == [], str(bg_called))
# ---------- 6. real_src 分层:③ 都没中 -> 启动后台扫盘(不阻塞返回 None) ----------
shell3 = tmp / "shell" / "ghost-skill"
shell3.mkdir(parents=True)
ocskill._REG = None
os.environ["OC_SKILL_ALT_ROOTS"] = str(tmp / "nowhere") # 指到不存在的根
real3 = ocskill.real_src(str(shell3))
check("6 没真身 -> 返回 None(走原诊断路径)", real3 is None, str(real3))
check("6 没真身 -> 启动后台扫盘", bg_called == ["ghost-skill"], str(bg_called))
# ---------- 7. 后台扫盘:单飞 + 扫完落库 skill.json ----------
ocskill.start_background_scan = _orig["bg"] # 还原真身
root_c = tmp / "roots" / "C"
mk_skill(root_c / "bg-skill-1", "bg-skill-1")
mk_skill(root_c / "bg-skill-2", "bg-skill-2")
(root_c / ".hidden-skill").mkdir() # 点前缀跳过
(root_c / "no-md-skill").mkdir() # 无 SKILL.md 跳过
reset_registry(tmp / "skill3.json")
ocskill.builtin_skill_roots = lambda distro=ocskill.DEFAULT_DISTRO: [str(root_c)]
ocskill.deep_scan_roots = lambda: []
# 放慢扫描以便验证单飞
_real_scan = ocskill._scan_root_skills
def _slow_scan(root):
time.sleep(0.4)
return _real_scan(root)
ocskill._scan_root_skills = _slow_scan
try:
started = ocskill.start_background_scan("x")
check("7 后台扫盘启动成功", started is True, str(started))
again = ocskill.start_background_scan("y")
check("7 后台扫盘单飞(运行中不叠加线程)", again is False, str(again))
deadline = time.time() + 10
while ocskill._BG_SCAN_RUNNING and time.time() < deadline:
time.sleep(0.05)
check("7 后台扫盘完成", not ocskill._BG_SCAN_RUNNING)
finally:
ocskill._scan_root_skills = _real_scan
skills = ocskill.load_registry().get("skills", {})
check("7 扫盘结果进内存注册表",
"bg-skill-1" in skills and "bg-skill-2" in skills, str(list(skills)))
check("7 点前缀 / 无 SKILL.md 不登记",
"no-md-skill" not in skills and ".hidden-skill" not in skills, str(list(skills)))
data3 = json.loads((tmp / "skill3.json").read_text(encoding="utf-8"))
check("7 扫盘结果落库 skill.json", "bg-skill-1" in data3.get("skills", {}),
str(data3)[:200])
# ---------- 8. 用户选定的有效目录优先(real_src 返回 None 不动) ----------
valid = mk_skill(tmp / "valid-skill", "valid-skill")
ocskill._REG = None
check("8 有效目录不动(选定优先)", ocskill.real_src(str(valid)) is None, str(valid))
# zip / SKILL.md 文件输入也不动
mdfile = valid / "SKILL.md"
check("8 SKILL.md 文件不动", ocskill.real_src(str(mdfile)) is None, str(mdfile))
# ---------- 9. deep_scan_roots 浅层扫描 ----------
old_cwd = os.getcwd()
work = tmp / "cwd"
(work / "someapp" / "skills").mkdir(parents=True)
try:
os.chdir(work)
deep = _orig["deep"]() # 用原函数(case7 把它补丁成了 lambda: [])
finally:
os.chdir(old_cwd)
key = os.path.normcase(os.path.abspath(str(work / "someapp" / "skills")))
check("9 扩展根扫到 <cwd>/someapp/skills",
key in [os.path.normcase(os.path.abspath(d)) for d in deep], str(deep))
check("9 扩展根无重复",
len([os.path.normcase(os.path.abspath(d)) for d in deep]) ==
len(set(os.path.normcase(os.path.abspath(d)) for d in deep)), str(len(deep)))
finally:
# ---------- 还原现场 ----------
ocskill.REGISTRY_PATH = _orig["REGISTRY_PATH"]
ocskill._REG = _orig["_REG"]
ocskill.builtin_skill_roots = _orig["builtin"]
ocskill.deep_scan_roots = _orig["deep"]
ocskill.start_background_scan = _orig["bg"]
if _orig["env_alt"] is None:
os.environ.pop("OC_SKILL_ALT_ROOTS", None)
else:
os.environ["OC_SKILL_ALT_ROOTS"] = _orig["env_alt"]
print()
if FAIL:
print(f"{len(FAIL)} FAILED: {FAIL}")
sys.exit(1)
print("ALL PASS")