Files
dpb/run_ci.py
T
34047007@qq.com b95053c52c init: 初始化 dpb 桃育种系统代码库
前后端 + 后端 FastAPI 全量源码、部署脚本与文档。
2026-08-06 00:17:49 +08:00

139 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""本机 CI stage:验收门 = 真实 PostgreSQL16 + Redis 就绪 → `pytest -m pg` 全绿。
背景:全工程无 git 仓库(root/backend/frontend 均无 .git),GitHub Actions 是云端服务,
本机用不上;CI stage 落地为本机驱动脚本。v8 验收标准「CI 跑通 pytest 全绿」= 本脚本 EXIT=0。
step 0 探测本机真实 PG16backend/env/.env.dev 的 DATABASE_*,可用 TC_CI_DB_* 覆盖)+ Redis
(REDIS_*)连通;任一失败给出明确指引并 exit 非 0。
step 1 backend 目录下 `pytest -m pg`dpb python + -X utf8)跑 9 套统计引擎正式 tc 套件
backend/tests/test_stat_analysis_tc.py 包装层,subprocess 调真实脚本)。
step 2 汇总 EXIT=0 → 「CI 全绿」;日志写 Temp/claude/ci_logs/。
说明:
- 全程真实 PG16 + Redis(本工程无 SQLite);PG/Redis 缺失时 step 0 拦截,不会误绿。
- 与 run_regression.py 并存:run_regression = 手动全量回归(e2e 探针 + tc 套件);
run_ci = 验收门(只跑 pytest -m pg 的 9 套正式 tc)。
- 解释器默认 sys.executable(须用 dpb env python 运行);可用 DPB_PYTHON 覆盖。
"""
import asyncio
import os
import subprocess
import sys
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
ROOT = os.path.dirname(os.path.abspath(__file__))
BACKEND = os.path.join(ROOT, "backend")
ENV_FILE = os.path.join(BACKEND, "env", ".env.dev")
LOG_DIR = os.path.join(ROOT, "Temp", "claude", "ci_logs")
PY = os.environ.get("DPB_PYTHON", sys.executable)
def _env_cfg() -> dict:
cfg = {}
if os.path.isfile(ENV_FILE):
for line in open(ENV_FILE, encoding="utf-8").read().splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
cfg[k.strip()] = v.strip().strip("'\"")
for key, envvar in {
"DATABASE_HOST": "TC_CI_DB_HOST", "DATABASE_PORT": "TC_CI_DB_PORT",
"DATABASE_USER": "TC_CI_DB_USER", "DATABASE_PASSWORD": "TC_CI_DB_PASSWORD",
"DATABASE_NAME": "TC_CI_DB_NAME", "REDIS_HOST": "TC_CI_REDIS_HOST",
"REDIS_PORT": "TC_CI_REDIS_PORT", "REDIS_DB_NAME": "TC_CI_REDIS_DB",
}.items():
if envvar in os.environ and os.environ[envvar] != "":
cfg[key] = os.environ[envvar]
return {
"DATABASE_HOST": cfg.get("DATABASE_HOST", "localhost"),
"DATABASE_PORT": int(cfg.get("DATABASE_PORT", "5432")),
"DATABASE_USER": cfg.get("DATABASE_USER", "dpb"),
"DATABASE_PASSWORD": cfg.get("DATABASE_PASSWORD", ""),
"DATABASE_NAME": cfg.get("DATABASE_NAME", "dpb"),
"REDIS_HOST": cfg.get("REDIS_HOST", "localhost"),
"REDIS_PORT": int(cfg.get("REDIS_PORT", "6379")),
"REDIS_DB_NAME": int(cfg.get("REDIS_DB_NAME", "1")),
}
async def _probe(cfg: dict) -> tuple[bool, bool, str, str]:
import asyncpg
import redis.asyncio as aioredis
db_ok = redis_ok = False
db_err = redis_err = ""
try:
conn = await asyncpg.connect(
host=cfg["DATABASE_HOST"], port=cfg["DATABASE_PORT"], user=cfg["DATABASE_USER"],
password=cfg["DATABASE_PASSWORD"] or None, database=cfg["DATABASE_NAME"], timeout=5)
await conn.close()
db_ok = True
except Exception as exc: # noqa: BLE001
db_err = str(exc)[:120]
try:
# protocol=2 必须与 app.core.database.redis_connect 一致:本机 WSL redis-server 版本较老,
# 不支持 redis-py 7.x 默认 RESP3,需走 RESP2。
r = aioredis.Redis(host=cfg["REDIS_HOST"], port=cfg["REDIS_PORT"],
db=cfg["REDIS_DB_NAME"], protocol=2, socket_connect_timeout=5)
await r.ping()
await r.aclose()
redis_ok = True
except Exception as exc: # noqa: BLE001
redis_err = str(exc)[:120]
return db_ok, redis_ok, db_err, redis_err
def main() -> int:
print("=" * 60)
print("本机 CI stagepytest -m pg10 套统计引擎正式 tc,真实 PG16 + Redis")
print("=" * 60)
# ---- step 0: 探测基础设施 ----
cfg = _env_cfg()
db_ok, redis_ok, db_err, redis_err = asyncio.run(_probe(cfg))
print(f"[step 0] 探测 PostgreSQL16 "
f"{cfg['DATABASE_HOST']}:{cfg['DATABASE_PORT']}/{cfg['DATABASE_NAME']} → "
f"{'OK' if db_ok else 'FAIL ' + db_err}")
print(f"[step 0] 探测 Redis {cfg['REDIS_HOST']}:{cfg['REDIS_PORT']}/{cfg['REDIS_DB_NAME']} → "
f"{'OK' if redis_ok else 'FAIL ' + redis_err}")
if not (db_ok and redis_ok):
print("\n❌ 基础设施未就绪,CI 无法放行。请检查:")
print(" · 本机 PostgreSQL 16 服务是否启动(服务名 postgresql-x64-16,端口 5432")
print(" · Redis 是否启动(redis-server,端口 6379;工程 WSL 内运行则确认 WSL 已起)")
print(f" · 连接配置 backend/env/.env.dev 的 DATABASE_*/REDIS_* 是否与本地一致")
print(" · 可用 TC_CI_DB_HOST / TC_CI_DB_PORT 等环境变量临时覆盖探测地址")
return 1
# ---- step 1: pytest -m pg ----
os.makedirs(LOG_DIR, exist_ok=True)
log_path = os.path.join(LOG_DIR, "ci_pytest_mg.log")
env = dict(os.environ)
env.setdefault("ENVIRONMENT", "dev")
env["PYTHONPATH"] = BACKEND
env["PYTHONIOENCODING"] = "utf-8"
cmd = [PY, "-X", "utf8", "-m", "pytest", "-m", "pg", "-v"]
print(f"[step 1] {' '.join(cmd)} (cwd={BACKEND})")
print(f" 日志 → {log_path}")
with open(log_path, "w", encoding="utf-8") as log:
rc = subprocess.call(cmd, cwd=BACKEND, env=env, stdout=log, stderr=subprocess.STDOUT)
# ---- step 2: 汇总 ----
with open(log_path, encoding="utf-8") as log:
tail = log.readlines()[-25:]
for line in tail:
print(" " + line.rstrip())
print("=" * 60)
if rc == 0:
print("✅ CI 全绿:pytest -m pg 10 套统计引擎正式 tc 全部通过")
return 0
print(f"❌ CI 失败:pytest -m pg EXIT={rc}(完整输出见 {log_path}")
return rc
if __name__ == "__main__":
sys.exit(main())