2026-07-27 17:12:09 +08:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
"""
|
|
|
|
|
全部单测一次跑完 (零外部依赖, 不连库)
|
|
|
|
|
======================================
|
|
|
|
|
运行: docker compose run --rm pms-web python scripts/run_tests.py
|
|
|
|
|
|
2026-09-03 11:51:42 +08:00
|
|
|
包含 (例数按 2026-09-03 实跑校正; 此前写的 591 是过期数字, 当时实跑已是 608):
|
2026-07-30 16:21:16 +08:00
|
|
|
test_core_units.py 仓位规划器 / 安全垫与成本账 (14 例)
|
|
|
|
|
test_batch2_units.py 命令状态机 / 方案生成器 / 回放对账纯逻辑 (35 例)
|
2026-09-03 16:06:27 +08:00
|
|
|
test_batch3_units.py 规则闸 / 择时执行器实现B 纯逻辑 / 用户止损价只披露不拦截 (26 例)
|
|
|
|
|
test_batch4_units.py 动作引擎 四类自主动作触发与数量口径 +
|
|
|
|
|
目标价到价产出清仓候选 (必定交人裁决) +
|
|
|
|
|
同轮只发一条减持 (到价清仓优先于保垫减仓) (15 例)
|
2026-07-30 16:21:16 +08:00
|
|
|
test_batch5_units.py 决策系统信号流解析与消化口径 (8 例)
|
2026-09-01 09:18:00 +08:00
|
|
|
test_batch6_units.py ws 通道: 测试向量/签名/公钥/水位/弃洞/DDL/逐笔入账 (68 例)
|
2026-09-03 11:51:42 +08:00
|
|
|
test_batch7_units.py 上游选股计划: 解析/新鲜度/候选筛选/取数守卫/
|
|
|
|
|
候选卡五键透传与截断/仅展示在候选阶段剔除 (35 例)
|
2026-07-31 13:28:10 +08:00
|
|
|
test_batch8_units.py 榜单变化: 名册指纹/三种语义/尾部闸/落库往返 (60 例)
|
2026-08-03 09:53:18 +08:00
|
|
|
test_batch9_units.py 成本价体检 / 对账按日推进 / 行业闸 / 取整记账 (49 例)
|
2026-09-03 16:06:27 +08:00
|
|
|
test_batch10_units.py 静默失败专项: 关键路径不许丢返回值 + 八条实例 +
|
|
|
|
|
页面能改的参数必须有人读 ([P] 组静态扫描) (47 例)
|
2026-08-03 12:49:01 +08:00
|
|
|
test_batch11_units.py 择时实现A: 本地检查等价/应答折算/缓存冷却/退B (24 例)
|
2026-08-06 14:41:59 +08:00
|
|
|
test_batch12_units.py 自主新建仓: 选票与滚动扣减/硬数字裁剪/漂移/预算/
|
2026-08-06 17:25:54 +08:00
|
|
|
转多信号插队与留痕/窗口末日只对命令/跳过原因分得开/
|
2026-08-25 11:09:58 +08:00
|
|
|
研判请求体的数据库类型 / 研判应答归一(UNAVAILABLE) (48 例)
|
2026-08-19 10:04:20 +08:00
|
|
|
test_batch13_units.py 决策系统卖出采纳: 门槛分档/清场标记/确认加速/闭仓清场 (4 例)
|
|
|
|
|
test_batch14_units.py 宏观择时: 指数计算与对齐/区域迟滞/周期与对数映射/
|
|
|
|
|
分方向触发/让路与冷却/宏观闸/建议采纳 (25 例)
|
2026-08-20 10:23:33 +08:00
|
|
|
test_batch15_units.py 紧急清仓修复: 配额在途口径不重复扣/紧急清仓不被
|
2026-08-20 11:09:33 +08:00
|
|
|
误判配额已出完/清仓命令即时撤策略/买入提议/在途买单;
|
|
|
|
|
在途与配额文案拆分; 减仓不掐策略腿 (12 例)
|
2026-08-25 11:09:58 +08:00
|
|
|
test_batch16_units.py 软归档 archived_at: 单表守卫/只归终态/列表默认排除 (5 例)
|
2026-08-25 13:45:51 +08:00
|
|
|
test_batch17_units.py 策略自动挂载: 定性归类(子串+保守优先)/网格参数生成/
|
2026-08-25 14:25:45 +08:00
|
|
|
科创板一手200(advisor 与 runner 两侧)/连边矩阵/
|
2026-08-26 10:01:30 +08:00
|
|
|
五阶段判定与逐票阶段返回/note 约定与冷却推导/
|
|
|
|
|
接力判定/clear_buypause/编排冒烟(dry_run 滴水不写/
|
2026-08-26 10:11:29 +08:00
|
|
|
名额/暂停买入/接力全链)/真跑写一跳摘要而预演不写/
|
|
|
|
|
判分脚本聚合与对照分组 (41 例)
|
2026-08-26 16:51:11 +08:00
|
|
|
test_batch18_units.py 登录与权限: 角色判定/会话票签验/bshop 返回解析/
|
2026-08-27 10:16:45 +08:00
|
|
|
接口鉴权(维护类归管理员) (16 例)
|
2026-08-28 13:07:27 +08:00
|
|
|
test_batch19_units.py 2026-08-28 审查修复回归: 科创板最小申报统一口径/
|
|
|
|
|
取整与部分卖/同轮买卖互斥/信号百分制契约/除权核销
|
|
|
|
|
缩放/日历按年降级/网格中枢与止盈闩锁/买入暂停
|
2026-08-28 14:34:00 +08:00
|
|
|
按来源分记/宏观失败路径保留留痕/双实例表名前缀 (19 例)
|
2026-09-03 11:51:42 +08:00
|
|
|
test_batch20_units.py 选股系统候选卡判决接入 (2026-09-03): 按判决分流三态/
|
|
|
|
|
无判决回退/判决认不出交人/仅展示不占名额/自动执行
|
|
|
|
|
开关四条件与任一缺失仍入队/信号来源区分/密钥名单
|
|
|
|
|
含会话密钥/研判键放行与必答题/置信度进硬数字/
|
|
|
|
|
页面静态守卫 (15 例)
|
2026-09-03 16:06:27 +08:00
|
|
|
test_batch21_units.py 减持的自动执行边界 (2026-09-03 安全修复): 保垫减仓照旧自动/
|
|
|
|
|
强制入队对卖出同样有效/研判不可用的减持入队/研究证据走弱
|
|
|
|
|
来源必定交人裁决/风控高置信卖出与命令清仓不经提议分流 (15 例)
|
|
|
|
|
test_wiring.py 装配自检: 服务层→核心→落表 全链路 (内存桩) +
|
|
|
|
|
目标价到价必定入队 (档位 full 也不自动卖) (69 例)
|
|
|
|
|
共 649 例
|
2026-07-27 17:12:09 +08:00
|
|
|
任一子集失败即整体失败 (退出码 1)。
|
2026-09-03 11:51:42 +08:00
|
|
|
|
|
|
|
|
哨兵位置清单 (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 与这两处断言
|
2026-09-03 16:06:27 +08:00
|
|
|
死参数名单 scripts/test_batch10_units.py 的 KNOWN_DEAD ([P] 组) 与 param_store.py 顶部
|
|
|
|
|
那段清点 —— 「页面能改而全库没人读」的参数只能同时改这两处。接通了、摘掉了、
|
|
|
|
|
或者又添了一个摆设, 都要在这里过一遍手
|
|
|
|
|
动作求值器 scripts/test_batch12_units.py 用例「回归·既有四类动作的判据与研判范围没被
|
|
|
|
|
动过」—— action_engine.EVALUATORS 的名单与次序钉在那里 (次序有意义: 减持排
|
|
|
|
|
在买入前面, 同轮买卖互斥才让得了路)
|
2026-07-27 17:12:09 +08:00
|
|
|
"""
|
|
|
|
|
import os
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
ROOT = os.path.dirname(HERE)
|
2026-07-28 09:10:07 +08:00
|
|
|
SUITES = ["test_core_units.py", "test_batch2_units.py", "test_batch3_units.py",
|
2026-07-28 15:48:57 +08:00
|
|
|
"test_batch4_units.py", "test_batch5_units.py", "test_batch6_units.py",
|
2026-07-31 13:46:15 +08:00
|
|
|
"test_batch7_units.py", "test_batch8_units.py", "test_batch9_units.py",
|
2026-08-06 13:55:27 +08:00
|
|
|
"test_batch10_units.py", "test_batch11_units.py", "test_batch12_units.py",
|
2026-08-20 10:23:33 +08:00
|
|
|
"test_batch13_units.py", "test_batch14_units.py", "test_batch15_units.py",
|
2026-08-26 16:51:11 +08:00
|
|
|
"test_batch16_units.py", "test_batch17_units.py",
|
2026-09-03 11:51:42 +08:00
|
|
|
"test_batch18_units.py", "test_batch19_units.py", "test_batch20_units.py",
|
2026-09-03 16:06:27 +08:00
|
|
|
"test_batch21_units.py",
|
2026-09-03 11:51:42 +08:00
|
|
|
"test_wiring.py"]
|
2026-07-27 17:12:09 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
2026-08-03 09:10:56 +08:00
|
|
|
# 先把「我到底在跑哪份代码」打出来。源码是打进镜像的, 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}")
|
|
|
|
|
|
2026-07-27 17:12:09 +08:00
|
|
|
failed = []
|
2026-08-03 09:10:56 +08:00
|
|
|
missing = []
|
2026-07-27 17:12:09 +08:00
|
|
|
for s in SUITES:
|
|
|
|
|
path = os.path.join(HERE, s)
|
|
|
|
|
if not os.path.exists(path):
|
2026-08-03 09:10:56 +08:00
|
|
|
# 少一个测试文件绝不能只印一行"跳过"就当没事 —— 镜像是旧的时候,
|
|
|
|
|
# 新增的那一批测试正好全部"不存在", 于是一条没跑却报 ALL SUITES PASS。
|
|
|
|
|
print(f"== 缺失 {s} —— **这一批一条都没跑**")
|
|
|
|
|
missing.append(s)
|
2026-07-27 17:12:09 +08:00
|
|
|
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)
|
2026-08-03 09:10:56 +08:00
|
|
|
if missing:
|
|
|
|
|
print(f"SUITE MISSING: {', '.join(missing)} —— 这些测试文件不在当前代码里, "
|
|
|
|
|
f"一条都没跑。镜像旧了就 make deploy; 真删了就把 SUITES 清单一起改")
|
2026-07-27 17:12:09 +08:00
|
|
|
if failed:
|
|
|
|
|
print(f"SUITE FAILED: {', '.join(failed)}")
|
|
|
|
|
sys.exit(1)
|
2026-08-03 09:10:56 +08:00
|
|
|
if missing:
|
|
|
|
|
sys.exit(1) # 「没跑」不许算「通过」
|
2026-07-27 17:12:09 +08:00
|
|
|
print("ALL SUITES PASS")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|