144 lines
5.8 KiB
Python
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)
|