# -*- 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 纯逻辑 / 用户止损价只披露不拦截 (26 例) test_batch4_units.py 动作引擎 四类自主动作触发与数量口径 + 目标价到价产出清仓候选 (必定交人裁决) + 同轮只发一条减持 (到价清仓优先于保垫减仓) + 跨轮减持让路 / 策略票仍评目标价 / 清仓按可卖量报数 (18 例) test_batch5_units.py 决策系统信号流解析与消化口径 (8 例) test_batch6_units.py ws 通道: 测试向量/签名/公钥/水位/弃洞/DDL/逐笔入账 (68 例) test_batch7_units.py 上游选股计划: 解析/新鲜度/候选筛选/取数守卫/ 候选卡五键透传与截断/仅展示在候选阶段剔除/ 研究理由两键透传与不进送研判名单 (36 例) test_batch8_units.py 榜单变化: 名册指纹/三种语义/尾部闸/落库往返 (60 例) test_batch9_units.py 成本价体检 / 对账按日推进 / 行业闸 / 取整记账 (49 例) test_batch10_units.py 静默失败专项: 关键路径不许丢返回值 + 八条实例 + 页面能改的参数必须有人读 ([P] 组静态扫描) (47 例) 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): 按判决分流三态/ 无判决回退/判决认不出交人/仅展示不占名额/自动执行 开关四条件与任一缺失仍入队/信号来源区分/密钥名单 含会话密钥/研判键放行与必答题/置信度进硬数字/ 仅展示的跳过原因写出这一只自己的判决依据/ 候选一只不剩时的说明不打印计数字典/ 新建仓低把握驳回按证据不足交人/ 页面静态守卫 (18 例) test_batch21_units.py 减持的自动执行边界 (2026-09-03 安全修复): 保垫减仓照旧自动/ 强制入队对卖出同样有效/研判不可用的减持入队/研究证据走弱 来源必定交人裁决/风控高置信卖出与命令清仓不经提议分流/ 到价提议挂着时止损照落并作废该提议 (17 例) test_batch25_units.py 解锁重问 (2026-09-09 方案第四之一节): 时段占比与量比换算/四条护栏/ 三条解锁条件与 fail-closed/请求体 force 为假时逐字节相同/名额只在真发 请求时用掉/判定零副作用/重问放行强制交人/九个参数登记 (14 例) test_batch24_units.py 点股票看它今天全部信号 (2026-09-09 方案第三节): 代码归一与跨市场不误配/ 分页回扫边界与按今天零点截断/五源解析与超龄标陈旧/去重同源合并跨源不合并/ 单源失败逐源标明与按票缓存/只读守卫/页面三处入口与层级 (9 例) test_batch23_units.py 公司质地与建议方案接入 (2026-09-09 接入方案): 解析层收公司深度两键/整句送研判可关/建议 方案矩阵与研判补档/持仓页公司块。 test_batch22_units.py 逻辑状态四态接入 (2026-09-07 第三件): 解析层收逻辑状态/硬数字带它而研判 白名单不收/判决候选而逻辑存疑强制交人/持仓存疑停增持侧/研究走弱 减持默认关且开了必交人/同轮减持优先级/早上取回与映射新鲜度/策略 买入腿按来源暂停恢复/持仓视图两栏三情形/假仓库签名/安全边际整句透传/参考目标价/催化事件与定价状态透传/两个期限的头 (19 例) test_batch26_units.py 建议档位与真下股数对齐 (2026-09-10): 卡上写试探仓 1% 采纳后就真只买 1%/ 提议卡重排要紧的在前证据收进展开/建议档位与实下股数不一致标出 (17 例) test_batch27_units.py 技术面接入·工作包一 (2026-09-11): 相位合成九格与四种无读数/震荡市抑制翻向/ 取数解析与代码归一/分页拼接与状态非 OK 不落表/映射只覆盖有当天读数的相关票/ 读数陈旧按无读数/参数登记与交易日差 (27 例) test_page_enum_guard.py 页面文案守卫 (静态扫描, 不连库不起浏览器): 枚举字段不许 直接印到页面上 / 判据码显示前必须剥前缀 / 不许把整个对象 打给交易员看 / 翻译兜底不许让英文码单独当句子 (1 例) test_wiring.py 装配自检: 服务层→核心→落表 全链路 (内存桩) + 目标价到价必定入队 (档位 full 也不自动卖) + 用户设的止损价与目标价单独成列显示 (70 例) 共 724 例 (总数按实跑逐批相加校正过两次: 曾写 649 是笔误, 实为 650; 09-03 先后加了同轮只发一条 减持与研究理由两键各一例, 到 652; 09-04 加了仅展示跳过原因与空候选说明各一例, 到 654; 又加了页面文案守卫一例, 到 655; 09-07 审查修复加了跨轮减持等五例, 到 660; 第二件低把握驳回交人一例, 到 661; 第三件逻辑状态接入第二十二批十五例, 到 676; 第四件安全边际整句透传一例, 到 677; 参考目标价一例, 到 678; 催化事件与定价状态透传一例, 到 679; 两个期限的头一例, 现为 680; 09-10 建议档位对齐第二十六批十七例到 697; 09-11 技术面接入工作包一第二十七批二十七例到 724) 任一子集失败即整体失败 (退出码 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 与这两处断言 死参数名单 scripts/test_batch10_units.py 的 KNOWN_DEAD ([P] 组) 与 param_store.py 顶部 那段清点 —— 「页面能改而全库没人读」的参数只能同时改这两处。接通了、摘掉了、 或者又添了一个摆设, 都要在这里过一遍手 页面枚举名单 scripts/test_page_enum_guard.py 顶部 ENUM_FIELDS —— 后端新增会送到页面的 枚举字段就登记一行, 并在 index.html 的 T 表里加对应的组。不登记等于不检查。 index.html 没有别的测试, 这道扫描是它唯一的护栏。 页面接线 scripts/test_page_wiring_guard.py —— 不用维护名单, 它从文件本身推。 查的是 setup() 里定义给模板用的东西有没有真的 return 出去。 2026-09-10 白屏 (propVerdictLine is not a function) 就是漏了十一个, 而那批从 09-04 埋下、每天都在用的页面上藏了六天才炸。 动作求值器 scripts/test_batch12_units.py 用例「回归·既有四类动作的判据与研判范围没被 动过」—— action_engine.EVALUATORS 的名单与次序钉在那里 (次序有意义: 减持排 在买入前面, 同轮买卖互斥才让得了路) """ 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_batch21_units.py", "test_batch22_units.py", "test_batch23_units.py", "test_batch24_units.py", "test_batch25_units.py", "test_batch26_units.py", "test_batch27_units.py", "test_page_enum_guard.py", "test_page_wiring_guard.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()