mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 15:34:16 +08:00
- 市场环境: 新增情绪周期6阶段(冰点/启动/主升/高潮/退潮/修复, 连板梯队驱动, EMA平滑+2日确认+弱档否决, 平均段长9.7天)与概念/行业主线排名(涨停梯队聚合, 可配置宽基/风格标签过滤); 市场环境页重构, regime 透明加列, 与5档state并存 - 挖掘: 因子与策略挖掘全链路(API/worker/进程锁/候选库/前端工作台/文档), 周度调度默认关闭且永不自动发布 - 回测: 财务快照因子(点时口径), 批量回测预计算共享下期收益, 信号路径矩阵列依赖展开修复(consecutive_limit_ups 缺列报错) - 数据/性能: enriched 生成与预热治理, 重任务限流, 行情/K线缓存复用, 时区修复 - 测试: 后端全量 914 通过; GUI 黑盒验证截图存证 gui-test-screenshots/
84 lines
2.2 KiB
Python
84 lines
2.2 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from contextlib import asynccontextmanager
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
import app.main as main_module
|
|
|
|
|
|
def test_lifespan_holds_mining_process_lock_around_application(monkeypatch) -> None:
|
|
events: list[str] = []
|
|
|
|
class LockStub:
|
|
def __init__(self, data_dir) -> None:
|
|
del data_dir
|
|
events.append("lock_created")
|
|
|
|
def acquire(self) -> None:
|
|
events.append("lock_acquired")
|
|
|
|
def release(self) -> None:
|
|
events.append("lock_released")
|
|
|
|
@asynccontextmanager
|
|
async def application_lifespan(_app):
|
|
events.append("application_started")
|
|
try:
|
|
yield
|
|
finally:
|
|
events.append("application_stopped")
|
|
|
|
monkeypatch.setattr(main_module, "MiningProcessLock", LockStub)
|
|
monkeypatch.setattr(main_module, "_application_lifespan", application_lifespan)
|
|
|
|
async def exercise() -> None:
|
|
async with main_module.lifespan(SimpleNamespace()):
|
|
events.append("request_serving")
|
|
|
|
asyncio.run(exercise())
|
|
|
|
assert events == [
|
|
"lock_created",
|
|
"lock_acquired",
|
|
"application_started",
|
|
"request_serving",
|
|
"application_stopped",
|
|
"lock_released",
|
|
]
|
|
|
|
|
|
def test_lifespan_releases_lock_when_application_shutdown_raises(monkeypatch) -> None:
|
|
events: list[str] = []
|
|
|
|
class LockStub:
|
|
def __init__(self, data_dir) -> None:
|
|
del data_dir
|
|
|
|
def acquire(self) -> None:
|
|
events.append("acquired")
|
|
|
|
def release(self) -> None:
|
|
events.append("released")
|
|
|
|
@asynccontextmanager
|
|
async def application_lifespan(_app):
|
|
try:
|
|
yield
|
|
finally:
|
|
raise RuntimeError("shutdown failed")
|
|
|
|
monkeypatch.setattr(main_module, "MiningProcessLock", LockStub)
|
|
monkeypatch.setattr(main_module, "_application_lifespan", application_lifespan)
|
|
|
|
async def exercise() -> None:
|
|
async with main_module.lifespan(SimpleNamespace()):
|
|
pass
|
|
|
|
with pytest.raises(RuntimeError, match="shutdown failed"):
|
|
asyncio.run(exercise())
|
|
|
|
assert events == ["acquired", "released"]
|