# ============================================================ # 内置算法库注册表黄金测试(矩阵 86 行 · 方向 E) # 覆盖:注册/查询、元数据完整(A-E 分类)、健康检查、版本查询、随机种子 # ============================================================ from __future__ import annotations import pytest from server.agent_core.algolib import ( CATEGORY_LABELS, AlgorithmManifest, AlgorithmRegistry, get_algolib, reset_algolib_registry, ) def _registry() -> AlgorithmRegistry: reset_algolib_registry() return get_algolib() def test_register_query_roundtrip(): """注册→查询→更新(upsert)→注销 全链路。""" reg = _registry() manifest = { "algo_id": "opt.grid_search", "name": "网格搜索调参", "category": "C", "description": "候选网格枚举(黄金测试资产)", "scale_limit": "<=1k 工单", "time_budget": "分钟级", "input_schema": {"grid": "dict"}, "output_schema": {"best": "dict"}, "golden_tests": ["tests/golden/test_param_opt.py"], "deterministic": True, "version": "1.0", } reg.register(manifest) got = reg.get("opt.grid_search") assert got is not None assert got["algo_id"] == "opt.grid_search" assert got["category"] == "C" # 查询:类别 + 关键词 hits = reg.query(category="C", keywords="网格") assert any(h["algo_id"] == "opt.grid_search" for h in hits) assert not reg.query(category="B", keywords="网格") # 重复注册 = upsert(版本升级) reg.register({**manifest, "version": "2.0"}) assert reg.get("opt.grid_search")["version"] == "2.0" # 注销 assert reg.unregister("opt.grid_search") is True assert reg.get("opt.grid_search") is None assert reg.unregister("opt.grid_search") is False def test_register_rejects_incomplete_metadata(): """元数据不完整 / 非确定性缺种子 注册必须拒绝。""" reg = _registry() with pytest.raises(ValueError): reg.register({"algo_id": "bad", "name": "缺类别"}) with pytest.raises(ValueError): reg.register({"algo_id": "bad2", "name": "非确定性无种子", "category": "C", "deterministic": False}) def test_builtin_catalog_metadata_complete_and_a_e_cover(): """内置目录:A-E 五类齐全,每条元数据完整(名称/适用条件/schema/版本/测试/种子)。""" reg = _registry() items = reg.list() assert len(items) >= 10 for m in items: problems = AlgorithmManifest(**m).validate_manifest() assert problems == [], f"{m['algo_id']} 元数据不完整: {problems}" assert set(CATEGORY_LABELS) == {"A", "B", "C", "D", "E"} for cat in "ABCDE": assert any(x["category"] == cat for x in items), f"缺 {cat} 类资产" ids = {x["algo_id"] for x in items} # 关键内置资产在位:A 规则 / B 精确 / C 元启发 / D ML 目录 / E 集成与评估链 assert {"rule.delivery_first", "rule.comprehensive"} <= ids assert "cp_sat" in ids assert "ga" in ids assert "ml.worktime_regression" in ids assert {"hybrid", "eval.sensitivity_tornado", "opt.parameter_optimizer"} <= ids # 非确定性算法都带随机种子(复现锚点) for m in items: if not m["deterministic"]: assert m["random_seed"] is not None, m["algo_id"] def test_health_check_ok_for_ready_and_fail_for_catalog_only(): """健康检查:可用内置算法 ok,目录资产如实报未就绪,自定义探针生效。""" reg = _registry() by_id = {h["algo_id"]: h for h in reg.health()} assert by_id["cp_sat"]["ok"] is True assert by_id["rule.delivery_first"]["ok"] is True assert by_id["eval.sensitivity_tornado"]["ok"] is True assert by_id["milp"]["ok"] is False # 未落地资产如实报告 # 自定义健康探针(健康/不健康) reg.register({"algo_id": "test.ok", "name": "健康算法", "category": "B", "health_fn": lambda: {"ok": True, "detail": "ready"}}) reg.register({"algo_id": "test.flaky", "name": "易挂算法", "category": "B", "health_fn": lambda: {"ok": False, "detail": "license expired"}}) assert reg.health("test.ok")[0]["ok"] is True assert reg.health("test.flaky")[0]["ok"] is False # 历史记录与未知 id assert reg.history("test.ok") assert reg.health("no.such.algo") == [] def test_version_query_and_random_seed_service(): """版本查询 + 随机种子服务(GA 等非确定性算法复现锚点)。""" reg = _registry() v = reg.version("rule.delivery_first") assert v is not None and v["version"] == "1.0" assert v["regen_strategy"] in ("llm", "manual", "hybrid") assert reg.version("no.such") is None # 升级版本后查询到新版本 reg.register({"algo_id": "rule.delivery_first", "name": "EDD 最早交期", "category": "A", "version": "2.1", "regen_strategy": "hybrid"}) assert reg.version("rule.delivery_first")["version"] == "2.1" # 随机种子:确定性算法返回声明种子(缺省 0),非确定性返回稳定种子 assert reg.next_seed("ga") == 20260802 assert reg.next_seed("rule.delivery_first") == 0 assert isinstance(reg.next_seed("no.such"), int)