## 假设被推翻
迁移计划曾把 akshare 的 py_mini_racer(62MB V8 预编译)列为「唯一硬阻塞点」,
理由是 Windows + cp312 是否有 wheel 未验证。
2026-10-26 用 uv 做离线解析(无需 Windows 机器):
uv pip install --python-platform windows --python-version 3.12 --dry-run akshare
→ 解析成功,mini-racer==0.14.1,与本机 macOS 上的版本完全一致
→ pandas/numpy 仅小版本差异(3.1.0/2.5.4 vs 本机 3.0.3/2.4.6)
## 但「能装」不等于「能跑」
V8 原生库在 win32 上能否真正加载并执行 JS 必须实测。故选在观察期的
windows job 里加三步实测(各自 continue-on-error,避免装不上时中断后续步骤):
1. 安装 akshare
2. 验证 py_mini_racer 可执行 JS(ctx.eval('1+1') == 2)
3. 跑 Services/akshare_fetcher/tests
## 一个必须写死的命名陷阱
PyPI 包名是 mini-racer,但**顶层模块名仍是 py_mini_racer**
(akshare 源码里全是 import py_mini_racer)。写成 import mini_racer 会
ModuleNotFoundError,从而误判为「Windows 装不上」。
本机已验证正确写法:import py_mini_racer → ctx.eval('1+1') == 2,
akshare 1.18.64 及其依赖 mini-racer 的模块均可正常导入。
首次 CI 反馈后:若三步全绿,可去掉 continue-on-error,并考虑把 tushare
一并纳入 Windows 范围。
238 lines
11 KiB
YAML
238 lines
11 KiB
YAML
name: CI
|
||
|
||
on:
|
||
push:
|
||
branches: [main, master]
|
||
pull_request:
|
||
workflow_dispatch:
|
||
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
jobs:
|
||
# ── Python 侧:版本一致性 + 语法 + 单元测试 ──────────────────────
|
||
python:
|
||
name: Python 3.12 一致性 / 语法 / 测试
|
||
runs-on: ubuntu-latest
|
||
# 真实 MySQL:给 replace 的事务语义做真库验证(fake 游标验不出回滚与半成品)
|
||
#
|
||
# 版本差异需知悉:CI 用 MySQL 8.0,生产是 26.7。事务/回滚语义一致(同为 InnoDB),
|
||
# 但 DDL 默认值与部分语法可能不同。集成套件只用 CREATE TABLE + UNIQUE KEY,
|
||
# 不含版本敏感语法;若将来要测分区表、生成列等特性,须先在此对齐版本。
|
||
#
|
||
# 密码为临时容器专用明文:service container 不暴露到公网,且随 runner 销毁。
|
||
services:
|
||
mysql:
|
||
image: mysql:8.0
|
||
env:
|
||
MYSQL_ROOT_PASSWORD: ci_root_pw_test_only
|
||
ports:
|
||
- 3306:3306
|
||
# 注:--default-authentication-plugin 仅为双保险(配合 cryptography)。
|
||
# MySQL 8.4 起该参数已移除、改用 --authentication-policy,
|
||
# 将来升级镜像版本需同步调整,否则容器启动即失败。
|
||
options: >-
|
||
--health-cmd="mysqladmin ping -h 127.0.0.1 -uroot -pci_root_pw_test_only"
|
||
--health-interval=10s
|
||
--health-timeout=5s
|
||
--health-retries=12
|
||
--default-authentication-plugin=mysql_native_password
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
# 版本来源唯一:读取仓库根目录 .python-version,
|
||
# 避免 CI 里再写死一份版本号造成漂移
|
||
- uses: actions/setup-python@v5
|
||
with:
|
||
python-version-file: '.python-version'
|
||
|
||
- name: 显示版本
|
||
run: python3 --version
|
||
|
||
- name: 版本一致性检查(声明 / venv / 硬编码引用)
|
||
run: python3 Scripts/bin/check_python_version.py --strict
|
||
|
||
- name: 语法编译检查
|
||
run: |
|
||
python3 -m compileall -q \
|
||
Scripts/bin \
|
||
Services/findatahub-core/src \
|
||
Services/kuaixun-service/app \
|
||
Services/kuaixun-service/extend \
|
||
Services/findatahub-client/src \
|
||
Services/tushare_fetcher/src \
|
||
Services/akshare_fetcher/src
|
||
|
||
- name: 安装测试依赖
|
||
# cryptography 是 pymysql 走 caching_sha2_password 的 **full path** 认证时
|
||
# 必需的(需要 RSA 公钥交换)。缺了会报 "cryptography package is required"。
|
||
#
|
||
# 注意一个容易误判的现象:本机某些 venv 没装 cryptography 也能连上库——
|
||
# 那是因为该 MySQL 实例的认证信息已被缓存,握手走了 fast path。
|
||
# CI 里是**全新容器、首次连接**,必然走 full path,所以这里必须装。
|
||
run: python3 -m pip install --quiet pytest pytest-asyncio cryptography
|
||
|
||
- name: 安装本地包(含各服务)
|
||
# findatahub-core 不在 PyPI 上,必须先本地安装;
|
||
# findatahub-client 的测试会 import 它。
|
||
# 用非 editable 安装:在本机 uv+setuptools 组合下 build_editable 会 EEXIST 失败,
|
||
# 非 editable 在各平台都稳。
|
||
run: |
|
||
python3 -m pip install --quiet ./Services/findatahub-core
|
||
python3 -m pip install --quiet ./Services/findatahub-client
|
||
python3 -m pip install --quiet -e ./Services/findatahub-core --no-build-isolation 2>/dev/null || true
|
||
|
||
- name: 统一测试入口自检
|
||
# 只验证解释器探测与套件声明是否可用,不跑测试(成本近乎 0)。
|
||
# 目的:防止 Scripts/bin/run_all_tests.py 随目录调整悄悄失效——
|
||
# 脚本腐烂时这里会以退出码 2 明确报错,而不是等到有人在本地踩坑。
|
||
run: python3 Scripts/bin/run_all_tests.py --list
|
||
|
||
- name: 运行单元测试(核心与采集器,纯离线)
|
||
# 这些用例全部为 mock 驱动、不连库,故设为阻塞性。
|
||
# integration 子目录单独跑(见下一步),此处排除以免重复。
|
||
run: |
|
||
python3 -m pytest Services/findatahub-core/tests -q --ignore=Services/findatahub-core/tests/integration
|
||
python3 -m pytest Services/findatahub-client/tests -q
|
||
python3 -m pytest Services/tushare_fetcher/tests/unit -q
|
||
python3 -m pytest Services/akshare_fetcher/tests -q
|
||
python3 -m pytest Scripts/bin/tests -q
|
||
|
||
- name: 真实库集成测试(replace 事务语义)
|
||
# 验证 fake 游标验不了的三件事:
|
||
# 1. 事务是否真的回滚(fake 的 rollback 只是置标志位)
|
||
# 2. 跨 chunk 失败是否留下「半成品」(fake 不会真提交,永远复现不出来)
|
||
# 3. 熔断时表是否真的没动
|
||
# 测试自建 fdh_it_replace_<随机> 空库并 DROP,不碰 service 里的任何现有库。
|
||
# 曾做证伪:把实现改回「每 chunk 提交」,本套件会报
|
||
# 「回滚不完整:表内行数既不是旧值」,证明用例有效。
|
||
env:
|
||
MYSQL_HOST: 127.0.0.1
|
||
MYSQL_PORT: "3306"
|
||
MYSQL_USER: root
|
||
MYSQL_PASSWORD: ci_root_pw_test_only
|
||
run: python3 -m pytest Services/findatahub-core/tests/integration -q
|
||
|
||
- name: 运行单元测试(kuaixun-service,部分用例可能依赖本机服务)
|
||
# 该套件含需要 MySQL / Ollama 的用例,暂不阻塞 CI
|
||
continue-on-error: true
|
||
run: python3 -m pytest Services/kuaixun-service/tests -q
|
||
|
||
# ── Swift 侧:编译验证 ────────────────────────────────────────
|
||
swift:
|
||
name: Swift 编译(macOS)
|
||
runs-on: macos-14
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: 显示工具链
|
||
run: swift --version
|
||
|
||
- name: 编译 release
|
||
# 本机 SPM manifest 沙箱(sandbox-exec)不可用,必须禁用
|
||
run: swift build -c release --disable-sandbox
|
||
|
||
- name: Shell 脚本语法检查
|
||
run: |
|
||
for f in Scripts/build-app.sh Scripts/bin/*.sh; do
|
||
bash -n "$f" && echo "ok: $f"
|
||
done
|
||
|
||
# ── Windows 侧:跨平台抽象层回归 ──────────────────────────────
|
||
#
|
||
# 目的:验证 `Scripts/bin/paths.py` 与 `process_utils.py` 在 win32 上真的可用,
|
||
# 而不是"写完就算"。这两个模块已接线 4 个脚本,一旦 Windows 分支写错,
|
||
# 表现是采集器静默不启动或 PID 检测失效——本地 macOS 上完全看不出来。
|
||
#
|
||
# 范围有意收窄:**不装 akshare**。它传递依赖 py_mini_racer(62MB V8 预编译二进制),
|
||
# Windows 上是否有 3.12 wheel 未验证,属已知硬阻塞点;
|
||
# 因此 Windows 侧只跑不依赖它的套件。tushare / akshare / backend 待后续单独验证。
|
||
windows:
|
||
name: Windows 跨平台回归
|
||
runs-on: windows-latest
|
||
# 观察期:首次接入,先不阻塞合并。
|
||
# 转正条件:连续两次 CI 全绿后删掉这一行,改为阻塞。
|
||
continue-on-error: true
|
||
env:
|
||
# Windows 控制台默认 cp936,中文用例名/日志会乱码甚至抛 UnicodeEncodeError。
|
||
# 采集器 stdout 是 JSON 状态协议,一旦被编码污染,Swift 端会误判"未运行"。
|
||
PYTHONUTF8: "1"
|
||
PYTHONIOENCODING: "utf-8"
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- uses: actions/setup-python@v5
|
||
with:
|
||
python-version-file: '.python-version'
|
||
|
||
- name: 显示版本
|
||
run: python --version
|
||
|
||
- name: 版本一致性检查
|
||
run: python Scripts/bin/check_python_version.py --strict
|
||
|
||
- name: 语法编译检查
|
||
run: >-
|
||
python -m compileall -q
|
||
Scripts/bin
|
||
Services/findatahub-core/src
|
||
Services/findatahub-client/src
|
||
|
||
- name: 安装依赖
|
||
# 显式列依赖而不装各服务的 requirements:
|
||
# 这里要验的是跨平台代码路径,不是完整运行环境。
|
||
run: |
|
||
python -m pip install --quiet pytest pytest-asyncio
|
||
python -m pip install --quiet ./Services/findatahub-core
|
||
python -m pip install --quiet ./Services/findatahub-client
|
||
python -m pip install --quiet pymysql httpx pandas sqlalchemy
|
||
|
||
- name: 跨平台抽象层测试(本次 Windows 接入的核心)
|
||
# 32 条用例锁定:paths 的 Windows 分支、process_utils 的
|
||
# OpenProcess / taskkill / creationflags、UTF-8 环境构造
|
||
run: python -m pytest Scripts/bin/tests/test_paths_process_utils.py -v
|
||
|
||
- name: 脚本层测试
|
||
# 含 PidManager、DB 池等;这批用例此前只在 POSIX 上跑过,
|
||
# Windows 上首次运行,暴露的问题正是需要修的
|
||
run: python -m pytest Scripts/bin/tests -q
|
||
|
||
- name: 数据层测试
|
||
# 排除 integration:Windows job 未接 MySQL service,跑它只会得到一片 skip
|
||
run: python -m pytest Services/findatahub-core/tests -q --ignore=Services/findatahub-core/tests/integration
|
||
|
||
- name: SDK 测试
|
||
run: python -m pytest Services/findatahub-client/tests -q
|
||
|
||
# ── akshare 可行性实测 ──────────────────────────────────
|
||
#
|
||
# 迁移计划曾把 akshare 的 py_mini_racer(62MB V8 预编译)列为「唯一硬阻塞点」,
|
||
# 理由是"Windows 上是否有 cp312 wheel 未验证"。
|
||
#
|
||
# 2026-10-26 用 uv 做离线解析,结论是**可以**:
|
||
# uv pip install --python-platform windows --python-version 3.12 --dry-run akshare
|
||
# → 解析成功,其中 mini-racer==0.14.1,与本机 macOS 上的版本完全一致
|
||
# (pandas/numpy 仅小版本差异:CI 3.1.0/2.5.4 vs 本机 3.0.3/2.4.6)
|
||
#
|
||
# 但"能装"不等于"能跑"——V8 二进制在 win32 上能否真正加载需要实测。
|
||
# 下面两步就是这次实测。各自 continue-on-error,避免装不上时中断后续步骤;
|
||
# 整个 job 本身也在观察期,失败不阻塞合并。
|
||
- name: 安装 akshare(Windows 可行性实测)
|
||
continue-on-error: true
|
||
run: python -m pip install --quiet akshare
|
||
|
||
- name: 验证 mini-racer 原生库可加载
|
||
# 注意包名与模块名不一致:PyPI 包叫 mini-racer,**顶层模块仍是 py_mini_racer**
|
||
# (akshare 源码里也全是 import py_mini_racer / from py_mini_racer import MiniRacer)。
|
||
# 写成 import mini_racer 会 ModuleNotFoundError 而误判为"Windows 装不上"。
|
||
continue-on-error: true
|
||
run: >-
|
||
python -c "import py_mini_racer;
|
||
ctx = py_mini_racer.MiniRacer();
|
||
print('py_mini_racer 可执行 JS:', ctx.eval('1+1'))"
|
||
|
||
- name: akshare 采集器测试
|
||
continue-on-error: true
|
||
run: python -m pytest Services/akshare_fetcher/tests -q
|