deliver: 交付收口(引擎代为提交)

This commit is contained in:
agent.develop 2026-09-22 22:03:22 +08:00
parent 4ad3468929
commit 37961262b4
4 changed files with 141 additions and 4 deletions

View File

@ -1,2 +1,56 @@
# pbls # pbls
PBL Agent OS 验证原型应用(唯一入口 `app/pbls.py`,端口以
`projects/pbls/env/test.json` 为唯一事实源)。业务模块见机构 `modules/pbl_*`。
## 测试依赖(DISC.2)
**结论:pytest 已显式声明,不再依赖临时注入 `PYTHONPATH`。** 宿主 `python3` 上没有 pytest
(`python3 -m pytest` → `No module named pytest`),且本机宿主 user site 是只读文件系统
(`pip install --user` 报 `[Errno 30] Read-only file system`),所以测试依赖的落点是 **venv**。
### 新增/相关文件清单
| 路径 | 层级 | 职责 |
|---|---|---|
| `apps/pbls/requirements-test.txt` | 应用级 | 测试前置依赖清单(`pytest>=7.0,<10`),由 `build.sh` **第 7b 步**装进应用 venv |
| `apps/pbls/build.sh` 第 7b 步 | 应用级 | `pip install -r requirements-test.txt` + `import pytest` 自证;`PBL_SKIP_TEST_DEPS=1` 可跳过 |
| `modules/pbl_evidence/requirements-dev.txt` | 模块级 | 同一版本区间的模块侧清单(不装本包也能跑离线单测的最短路径) |
| `modules/pbl_evidence/scripts/setup_test_env.sh` | 模块级 | 建 `.venv-test` 并安装 `requirements-dev.txt`(含仓库卫生门禁) |
| `modules/pbl_evidence/scripts/activate_test_env.sh` | 模块级 | 打印激活/直接调用测试 venv 解释器的方式 |
| `modules/pbl_evidence/scripts/run_tests.sh` | 模块级 | 一条命令跑测试:无 pytest 时自动引导,再执行 pytest |
| `modules/pbl_evidence/README.md` §Running tests | 文档 | 排障入口 + `PBL_EVIDENCE_TEST_DB` 开关用法 |
### 为什么是两份清单(应用级 vs 模块级)
- `requirements-test.txt`(应用级)服务**整应用**的测试环境:跑 `build.sh` 后 venv 内即有 pytest,
任何 `modules/pbl_*/tests/` 都能用同一个解释器执行。
- `requirements-dev.txt`(模块级)服务**只测本模块**的 CI/离线场景:不安装 pbls 应用、不装本包
(`pip install -e .`)也能一条命令装好依赖。
- 两份**版本区间同源**(`>=7.0,<10`),避免"应用装了 9.x、模块 CI 装了 7.x"导致用例语义漂移。
- 两者都刻意与运行时清单 `requirements.txt` 分离:pytest 不进生产镜像,也不进
`build.sh` 第 5 步的运行时依赖核验(该步只核验 ahserver/sqlor/apppublic/appbase/rbac/PyMySQL)。
### 推荐调用顺序
```bash
# 场景 1:完整应用环境(部署/联调机)
bash apps/pbls/build.sh # 第 7 步 venv → 7b 测试依赖 → 8 装模块
source apps/pbls/venv/bin/activate
python3 -m pytest modules/pbl_evidence/tests/test_m5a_idempotency.py -v
# 场景 2:只跑 pbl_evidence 单测(测试机/CI,最短路径)
bash modules/pbl_evidence/scripts/run_tests.sh # 无 pytest 时自动引导 .venv-test
# 场景 3:只要环境不要跑(手工用 venv 解释器)
bash modules/pbl_evidence/scripts/setup_test_env.sh
bash modules/pbl_evidence/scripts/activate_test_env.sh
```
### 期望结果
- 不带 `PBL_EVIDENCE_TEST_DB`:`8 passed, 1 skipped`,RC=0(`test_double_collect_is_idempotent_with_db`
需真实 MariaDB,未设开关时按设计 SKIPPED,属预期)。
- 带 `PBL_EVIDENCE_TEST_DB`(用法见 `modules/pbl_evidence/README.md` §Running tests):`9 passed`。
- 实测日志:`projects/pbls/docs/02-develop/m5a-disc2-pytest-run.log`。
- 禁止为凑通过修改测试断言逻辑。

View File

@ -98,6 +98,27 @@ fi
"${PY}" -m pip install --upgrade pip wheel setuptools "${PY}" -m pip install --upgrade pip wheel setuptools
"${PY}" -m pip install -r "${APP_DIR}/requirements.txt" "${PY}" -m pip install -r "${APP_DIR}/requirements.txt"
# ---- 7b. 测试前置依赖(DISC.2)----
# 运行时依赖(requirements.txt)刻意不含 pytest;测试依赖单列 requirements-test.txt,
# 由本步装进同一个 venv,使
# source venv/bin/activate && python3 -m pytest tests/test_m5a_idempotency.py -v
# 在**不手工注入 PYTHONPATH** 的前提下可直接执行(此前靠 PYTHONPATH=<ws>/.pylibs
# 临时注入跑通,换机器/换角色即失效,test 角色会误判为环境不可用)。
# 只要运行时依赖的场景(如生产镜像)用 PBL_SKIP_TEST_DEPS=1 显式跳过。
REQ_TEST="${APP_DIR}/requirements-test.txt"
if [ "${PBL_SKIP_TEST_DEPS:-0}" = "1" ]; then
log "PBL_SKIP_TEST_DEPS=1:跳过测试前置依赖(venv 内将没有 pytest,测试角色需自行引导)"
elif [ ! -f "${REQ_TEST}" ]; then
die "缺测试依赖清单 ${REQ_TEST}(DISC.2 要求显式声明;确要跳过请设 PBL_SKIP_TEST_DEPS=1)"
else
log "安装测试前置依赖: ${REQ_TEST}"
"${PY}" -m pip install -r "${REQ_TEST}"
# 自证:装完必须真能 import + 跑 --version,否则本步等于没做(不让注释与事实矛盾)
"${PY}" -c 'import pytest' >/dev/null 2>&1 \
|| die "安装后 venv 内仍无法 import pytest(${PY})"
log "7b 自证通过: $("${PY}" -m pytest --version 2>&1 | head -1) @ ${PY}"
fi
# ---- 8. 本地安装 15 个 pbl_* 模块 + 复用模块(可编辑模式)---- # ---- 8. 本地安装 15 个 pbl_* 模块 + 复用模块(可编辑模式)----
for mod_dir in "${WS_DIR}"/modules/pbl_*; do for mod_dir in "${WS_DIR}"/modules/pbl_*; do
[ -d "${mod_dir}" ] || continue [ -d "${mod_dir}" ] || continue

View File

@ -3,16 +3,21 @@
# 为什么单列一份、不并进 requirements.txt: # 为什么单列一份、不并进 requirements.txt:
# requirements.txt 是**运行时**依赖,会被 build.sh 装进生产/测试 venv 并随应用 # requirements.txt 是**运行时**依赖,会被 build.sh 装进生产/测试 venv 并随应用
# 部署;pytest 只在「跑测试」这一步需要,混进去等于把测试工具塞进生产镜像。 # 部署;pytest 只在「跑测试」这一步需要,混进去等于把测试工具塞进生产镜像。
# 因此本文件由 build.sh **第 7b 步**单独安装(可用 PBL_SKIP_TEST_DEPS=1 跳过),
# 不进入运行时依赖核验清单(第 5 步只核验 ahserver/sqlor/apppublic/appbase/rbac/PyMySQL)。
# #
# 为什么必须显式声明: # 为什么必须显式声明:
# 测试机宿主 python3 未装 pytest,`python3 -m pytest` 直接 # 测试机宿主 python3 未装 pytest,`python3 -m pytest` 直接
# `No module named pytest`;此前靠临时注入 PYTHONPATH=<workspace>/.pylibs 跑通 # `No module named pytest`;此前靠临时注入 PYTHONPATH=<workspace>/.pylibs 跑通
# (一次性手段,换机器/换角色即失效),test 角色据此会误判「用例失败/环境不可用」。 # (一次性手段,换机器/换角色即失效),test 角色据此会误判「用例失败/环境不可用」。
# 宿主 user site(/d/pipeline/.local)在本机是只读文件系统,`pip install --user`
# 也会失败,所以「让 pytest 可用」的正确落点是 venv,而不是宿主 python3。
# #
# 用法(build.sh 第 7b 步已自动执行): # 用法(三条都是真实可执行路径,与 apps/pbls/README.md「测试依赖(DISC.2)」一致):
# python3 -m pip install -r requirements-test.txt # 1) bash build.sh # 第 7b 步自动 pip install -r 本文件到 venv
# 或 bash scripts/build.sh # venv 内即有 pytest # source venv/bin/activate && python3 -m pytest <目标> -v
# 或 cd modules/pbl_evidence && bash scripts/run_tests.sh # 模块自带引导 # 2) python3 -m pip install -r requirements-test.txt # 自己管环境时(需可写 site)
# 3) bash modules/pbl_evidence/scripts/run_tests.sh # 模块自带引导(无 pytest 时自动建 .venv-test)
# #
# 版本区间与各模块 pyproject [project.optional-dependencies].test 一致(>=7.0,<10)。 # 版本区间与各模块 pyproject [project.optional-dependencies].test 一致(>=7.0,<10)。
pytest>=7.0,<10 pytest>=7.0,<10

57
scripts/patch_build_7b.py Normal file
View File

@ -0,0 +1,57 @@
#!/usr/bin/env python3
"""一次性补丁脚本:在 apps/pbls/build.sh 第 7 步后插入「7b. 测试前置依赖(DISC.2)」。
为什么用脚本而不是 sed:build.sh 里含 `${...}` 与引号,shell 内嵌补丁容易吃掉转义。
本脚本幂等——已存在 7b 标记时直接退出,不重复插入。
"""
import io
import os
import sys
BUILD = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "build.sh")
ANCHOR = '# ---- 8. 本地安装 15 个 pbl_* 模块 + 复用模块(可编辑模式)----'
MARK = '# ---- 7b. 测试前置依赖(DISC.2)----'
BLOCK = '''# ---- 7b. 测试前置依赖(DISC.2)----
# 运行时依赖(requirements.txt)刻意不含 pytest;测试依赖单列 requirements-test.txt,
# 由本步装进同一个 venv,使
# source venv/bin/activate && python3 -m pytest tests/test_m5a_idempotency.py -v
# 在**不手工注入 PYTHONPATH** 的前提下可直接执行(此前靠 PYTHONPATH=<ws>/.pylibs
# 临时注入跑通,换机器/换角色即失效,test 角色会误判为环境不可用)。
# 只要运行时依赖的场景(如生产镜像)用 PBL_SKIP_TEST_DEPS=1 显式跳过。
REQ_TEST="${APP_DIR}/requirements-test.txt"
if [ "${PBL_SKIP_TEST_DEPS:-0}" = "1" ]; then
log "PBL_SKIP_TEST_DEPS=1:跳过测试前置依赖(venv 内将没有 pytest,测试角色需自行引导)"
elif [ ! -f "${REQ_TEST}" ]; then
die "缺测试依赖清单 ${REQ_TEST}(DISC.2 要求显式声明;确要跳过请设 PBL_SKIP_TEST_DEPS=1)"
else
log "安装测试前置依赖: ${REQ_TEST}"
"${PY}" -m pip install -r "${REQ_TEST}"
# 自证:装完必须真能 import + 跑 --version,否则本步等于没做(不让注释与事实矛盾)
"${PY}" -c 'import pytest' >/dev/null 2>&1 \\
|| die "安装后 venv 内仍无法 import pytest(${PY})"
log "7b 自证通过: $("${PY}" -m pytest --version 2>&1 | head -1) @ ${PY}"
fi
'''
def main():
build = os.path.abspath(BUILD)
with io.open(build, "r", encoding="utf-8") as f:
text = f.read()
if MARK in text:
print("SKIP: 7b 已存在,未重复插入")
return 0
if ANCHOR not in text:
print("FAIL: 找不到第 8 步锚点,拒绝盲目修改")
return 1
patched = text.replace(ANCHOR, BLOCK + ANCHOR, 1)
with io.open(build, "w", encoding="utf-8") as f:
f.write(patched)
print("OK: 已在 %s 第 8 步前插入 7b(+%d 字符)" % (build, len(patched) - len(text)))
return 0
if __name__ == "__main__":
sys.exit(main())