tradingSystem/scripts/test_batch35_units.py

144 lines
5.8 KiB
Python

# -*- coding: utf-8 -*-
"""第三十五批:单测隔离与观察读数内存仓储,全部离线。"""
import unit_test_guard # noqa: F401
from datetime import datetime
import inspect
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import traceback
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from app.repo import consensus_stat_repo as csr
from app.services import consensus_stats as stats, proposal_service
from test_wiring import install_fakes, _target_fakes
RESULTS = []
def case(name):
def deco(fn):
RESULTS.append((name, fn))
return fn
return deco
@case("仓储四个入口均替换,签名与真实函数一致")
def _():
originals = {name: getattr(csr, name) for name in
("upsert_many", "delete_day_kind", "list_range", "count_by_kind")}
fake = install_fakes()
for name, original in originals.items():
bound = getattr(csr, name)
assert bound.__self__ is fake.consensus_stats
def shape(fn):
return [(p.name, p.kind, p.default) for p in inspect.signature(fn).parameters.values()]
assert shape(original) == shape(bound), name
@case("重复读数累加轮次且保留首次时间,读取与日报都用内存")
def _():
fake = install_fakes()
out = {"self_decide_seen": [{"ts_code": "600000.SH", "decision": "accept",
"why": "target_price", "action": "EXIT"}]}
first, last = datetime(2026, 9, 18, 9, 31), datetime(2026, 9, 18, 9, 32)
for now in (first, last):
assert stats.record_round(out, {}, {}, now)["ok"]
rows = csr.list_range(20260918, 20260918, ["auto_accept"])
assert len(rows) == 1 and rows[0]["rounds"] == 2, rows
assert rows[0]["first_at"] == first and rows[0]["last_at"] == last, rows
assert stats.daily_summary(20260918)["auto_accept"] == {"rows": 1, "rounds": 2}
assert len(fake.consensus_stats.rows) == 1
@case("整轮目标价扫描的自决读数落入内存,试算不写")
def _():
fake, restore = _target_fakes(13.0, 12.5, params={"PMS_AUTONOMY": "full",
"PMS_SELF_DECIDE": "full"})
try:
proposal_service.scan_and_route(now=datetime(2026, 9, 18, 10, 30), dry_run=True)
assert not fake.consensus_stats.rows
result = proposal_service.scan_and_route(now=datetime(2026, 9, 18, 10, 30))
rows = csr.list_range(20260918, 20260918, ["auto_accept"])
assert result["ok"] and len(rows) == 1, (result, rows)
assert rows[0]["ts_code"] == "600000.SH"
finally:
restore()
@case("重建覆盖先清当日无读数,保留其他日期与类别")
def _():
install_fakes()
for day in (17, 18):
assert stats.record_map_cover(relevant_codes=["600000.SH", "000001.SZ"],
state_codes=[], now=datetime(2026, 9, day, 6, 30))["ok"]
assert stats.record_map_cover(relevant_codes=["600000.SH", "000001.SZ"],
state_codes=["600000.SH"], now=datetime(2026, 9, 18, 8, 40))["ok"]
today = csr.list_range(20260918, 20260918, ["tech_noread"])
assert [r["ts_code"] for r in today] == ["000001.SZ"], today
assert len(csr.list_range(20260917, 20260917, ["tech_noread"])) == 2
assert stats.daily_summary(20260918)["map_cover"] == {"rows": 1, "rounds": 2}
@case("重新装桩清空读数,不把前一用例的数据带入下一例")
def _():
first = install_fakes()
csr.upsert_many([{"stat_date": 20260918, "ts_code": "600000.SH", "kind": "auto_accept"}])
second = install_fakes()
assert first.consensus_stats.rows and not second.consensus_stats.rows
assert csr.list_range(20260918, 20260918) == []
def _child(source, env=None, dotenv=None):
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "probe.py"
path.write_text("import sys\nsys.path.insert(0, %r)\n" % str(ROOT) + source)
if dotenv:
(Path(tmp) / ".env").write_text(dotenv)
return subprocess.run([sys.executable, str(ROOT / "scripts/unit_test_guard.py"), str(path)],
cwd=tmp, env={**os.environ, **(env or {})}, text=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=20)
@case("联网异常被吞掉也必须失败,阻断发生在建立连接之前")
def _():
r = _child("import socket\ntry:\n socket.socket().connect(('127.0.0.1', 1))\n"
"except Exception:\n pass\nprint('swallowed')\n")
assert "swallowed" in r.stdout and r.returncode == 1, r
assert "TEST ISOLATION FAILED" in r.stderr and "socket.connect" in r.stderr, r.stderr
@case("测试入口不继承生产参数,也不读启动目录的 .env")
def _():
r = _child("from config.settings import settings\n"
"assert settings.PMS_TOTAL_SCALE == 2000000\nprint('clean')\n",
env={"PMS_TOTAL_SCALE": "987654"}, dotenv="PMS_TOTAL_SCALE=123456\n")
assert r.returncode == 0 and "clean" in r.stdout, (r.stdout, r.stderr)
@case("所有业务测试独立运行时也安装联网护栏")
def _():
import ast
import run_tests
for name in run_tests.SUITES:
tree = ast.parse((ROOT / "scripts" / name).read_text())
imports = [n for n in tree.body if isinstance(n, (ast.Import, ast.ImportFrom))]
assert isinstance(imports[0], ast.Import) and imports[0].names[0].name == "unit_test_guard", name
if __name__ == "__main__":
ok = 0
for name, fn in RESULTS:
try:
fn()
ok += 1
print(" ok " + name)
except Exception:
print(" FAIL " + name)
traceback.print_exc()
print("ALL PASS (%d cases)" % ok if ok == len(RESULTS) else "FAILED %d/%d" % (len(RESULTS)-ok, len(RESULTS)))
sys.exit(0 if ok == len(RESULTS) else 1)