158 lines
12 KiB
Python
158 lines
12 KiB
Python
# -*- 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_batch23_units.py 公司质地与建议方案接入 (2026-09-09 接入方案): 解析层收公司深度两键/整句送研判可关/建议
|
|
方案矩阵与研判补档/持仓页公司块。
|
|
test_batch22_units.py 逻辑状态四态接入 (2026-09-07 第三件): 解析层收逻辑状态/硬数字带它而研判
|
|
白名单不收/判决候选而逻辑存疑强制交人/持仓存疑停增持侧/研究走弱
|
|
减持默认关且开了必交人/同轮减持优先级/早上取回与映射新鲜度/策略
|
|
买入腿按来源暂停恢复/持仓视图两栏三情形/假仓库签名/安全边际整句透传/参考目标价/催化事件与定价状态透传/两个期限的头 (19 例)
|
|
test_page_enum_guard.py 页面文案守卫 (静态扫描, 不连库不起浏览器): 枚举字段不许
|
|
直接印到页面上 / 判据码显示前必须剥前缀 / 不许把整个对象
|
|
打给交易员看 / 翻译兜底不许让英文码单独当句子 (1 例)
|
|
test_wiring.py 装配自检: 服务层→核心→落表 全链路 (内存桩) +
|
|
目标价到价必定入队 (档位 full 也不自动卖) +
|
|
用户设的止损价与目标价单独成列显示 (70 例)
|
|
共 680 例
|
|
(总数按实跑逐批相加校正过两次: 曾写 649 是笔误, 实为 650; 09-03 先后加了同轮只发一条
|
|
减持与研究理由两键各一例, 到 652; 09-04 加了仅展示跳过原因与空候选说明各一例, 到 654; 又加了页面文案守卫一例, 到 655; 09-07 审查修复加了跨轮减持等五例, 到 660; 第二件低把握驳回交人一例, 到 661; 第三件逻辑状态接入第二十二批十五例, 到 676; 第四件安全边际整句透传一例, 到 677; 参考目标价一例, 到 678; 催化事件与定价状态透传一例, 到 679; 两个期限的头一例, 现为 680)
|
|
任一子集失败即整体失败 (退出码 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_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_page_enum_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()
|