# -*- coding: utf-8 -*- """ 全部单测一次跑完 (零外部依赖, 不连库) ====================================== 运行: docker compose run --rm pms-web python scripts/run_tests.py 包含 (例数按 2026-09-03 实跑校正; 此前写的 591 是过期数字, 当时实跑已是 608): test_core_units.py 仓位规划器 / 安全垫与成本账 (14 例) test_batch2_units.py 命令状态机 / 方案生成器 / 回放对账纯逻辑 (35 例) test_batch3_units.py 规则闸 / 择时执行器实现B 纯逻辑 (25 例) test_batch4_units.py 动作引擎 四类自主动作触发与数量口径 (11 例) test_batch5_units.py 决策系统信号流解析与消化口径 (8 例) test_batch6_units.py ws 通道: 测试向量/签名/公钥/水位/弃洞/DDL/逐笔入账 (68 例) test_batch7_units.py 上游选股计划: 解析/新鲜度/候选筛选/取数守卫/ 候选卡五键透传与截断/仅展示在候选阶段剔除 (35 例) test_batch8_units.py 榜单变化: 名册指纹/三种语义/尾部闸/落库往返 (60 例) test_batch9_units.py 成本价体检 / 对账按日推进 / 行业闸 / 取整记账 (49 例) test_batch10_units.py 静默失败专项: 关键路径不许丢返回值 + 八条实例 (45 例) test_batch11_units.py 择时实现A: 本地检查等价/应答折算/缓存冷却/退B (24 例) test_batch12_units.py 自主新建仓: 选票与滚动扣减/硬数字裁剪/漂移/预算/ 转多信号插队与留痕/窗口末日只对命令/跳过原因分得开/ 研判请求体的数据库类型 / 研判应答归一(UNAVAILABLE) (48 例) test_batch13_units.py 决策系统卖出采纳: 门槛分档/清场标记/确认加速/闭仓清场 (4 例) test_batch14_units.py 宏观择时: 指数计算与对齐/区域迟滞/周期与对数映射/ 分方向触发/让路与冷却/宏观闸/建议采纳 (25 例) test_batch15_units.py 紧急清仓修复: 配额在途口径不重复扣/紧急清仓不被 误判配额已出完/清仓命令即时撤策略/买入提议/在途买单; 在途与配额文案拆分; 减仓不掐策略腿 (12 例) test_batch16_units.py 软归档 archived_at: 单表守卫/只归终态/列表默认排除 (5 例) test_batch17_units.py 策略自动挂载: 定性归类(子串+保守优先)/网格参数生成/ 科创板一手200(advisor 与 runner 两侧)/连边矩阵/ 五阶段判定与逐票阶段返回/note 约定与冷却推导/ 接力判定/clear_buypause/编排冒烟(dry_run 滴水不写/ 名额/暂停买入/接力全链)/真跑写一跳摘要而预演不写/ 判分脚本聚合与对照分组 (41 例) test_batch18_units.py 登录与权限: 角色判定/会话票签验/bshop 返回解析/ 接口鉴权(维护类归管理员) (16 例) test_batch19_units.py 2026-08-28 审查修复回归: 科创板最小申报统一口径/ 取整与部分卖/同轮买卖互斥/信号百分制契约/除权核销 缩放/日历按年降级/网格中枢与止盈闩锁/买入暂停 按来源分记/宏观失败路径保留留痕/双实例表名前缀 (19 例) test_batch20_units.py 选股系统候选卡判决接入 (2026-09-03): 按判决分流三态/ 无判决回退/判决认不出交人/仅展示不占名额/自动执行 开关四条件与任一缺失仍入队/信号来源区分/密钥名单 含会话密钥/研判键放行与必答题/置信度进硬数字/ 页面静态守卫 (15 例) test_wiring.py 装配自检: 服务层→核心→落表 全链路 (内存桩) (67 例) 共 626 例 任一子集失败即整体失败 (退出码 1)。 哨兵位置清单 (2026-09-03 抄录; 改了对应的东西就得来这些地方改断言, 断言不动就是漏了): DDL 表数 scripts/test_batch6_units.py 用例「DDL 文件本身体检通过」(约第 580 行), 断言 `eq(len([... k == "table"]), 19)` (约第 593 行) —— 加表就 +1 调度位 scripts/test_wiring.py 用例「装配·调度表覆盖设计 §10 全部调度位」(约第 654 行), beat_schedule 名字集合与 pms.* 任务名两处 路由清单 scripts/test_wiring.py 用例「装配·API 路由齐全 (四块页面 + 运维)」(约第 634 行) 的 need 列表 —— 加接口就登记 批次登记 本文件 SUITES 列表 + 上面的例数表 —— 加测试文件必须两处同改, 否则「缺失」 会被判失败 (「没跑」不许算「通过」) 软失败函数 scripts/test_batch10_units.py 顶部 SOFT_FAIL 名单 (约第 51 行) 与用例 [A1] (约第 95 行, AST 扫 app/ 全库) —— 新加「失败不抛异常只回 ok=False」的函数就登记 桩签名比对 scripts/test_batch10_units.py 用例 [M1] (约第 836 行): 从源码读 pms_repo 真实 签名与 test_wiring.FakeRepo 逐个比 —— 改 repo 函数签名, 桩必须同改 密钥名单 scripts/test_batch6_units.py 用例「密钥不进 ParamStore (协议 §10.1.1)」(约第 642 行) 与 scripts/test_batch20_units.py 用例「密钥名单·会话密钥」—— 新增密钥类配置要同时进 param_store.SECRET_KEYS 与这两处断言 """ import os import subprocess import sys HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.dirname(HERE) SUITES = ["test_core_units.py", "test_batch2_units.py", "test_batch3_units.py", "test_batch4_units.py", "test_batch5_units.py", "test_batch6_units.py", "test_batch7_units.py", "test_batch8_units.py", "test_batch9_units.py", "test_batch10_units.py", "test_batch11_units.py", "test_batch12_units.py", "test_batch13_units.py", "test_batch14_units.py", "test_batch15_units.py", "test_batch16_units.py", "test_batch17_units.py", "test_batch18_units.py", "test_batch19_units.py", "test_batch20_units.py", "test_wiring.py"] def main(): # 先把「我到底在跑哪份代码」打出来。源码是打进镜像的, git pull 之后不 build 的话 # 这里跑的还是旧代码, 而它照样会 ALL SUITES PASS —— `make test` 会拿这个指纹跟 # 工作树对一遍, 对不上就喊。详见 scripts/code_fingerprint.py。 try: from code_fingerprint import fingerprint print(f"代码指纹 {fingerprint()} (与工作树对不上 = 容器跑的是旧镜像, 先 make deploy)") except Exception as e: print(f"代码指纹取不到: {type(e).__name__}: {e}") failed = [] missing = [] for s in SUITES: path = os.path.join(HERE, s) if not os.path.exists(path): # 少一个测试文件绝不能只印一行"跳过"就当没事 —— 镜像是旧的时候, # 新增的那一批测试正好全部"不存在", 于是一条没跑却报 ALL SUITES PASS。 print(f"== 缺失 {s} —— **这一批一条都没跑**") missing.append(s) continue print(f"\n=== {s} " + "=" * (52 - len(s))) r = subprocess.run([sys.executable, path], cwd=ROOT) if r.returncode != 0: failed.append(s) print("\n" + "=" * 62) if missing: print(f"SUITE MISSING: {', '.join(missing)} —— 这些测试文件不在当前代码里, " f"一条都没跑。镜像旧了就 make deploy; 真删了就把 SUITES 清单一起改") if failed: print(f"SUITE FAILED: {', '.join(failed)}") sys.exit(1) if missing: sys.exit(1) # 「没跑」不许算「通过」 print("ALL SUITES PASS") if __name__ == "__main__": main()