79441a8a87
D4 文档/规划/历史混放收口:
- docs/TECH_DEBT.md §2 由'按批次回顾'精简为'按主题摘要',
与§3重复内容(批次0-4详细展开)整体迁入
docs/archive/2026-09_governance_batches.md
- docs/STATUS.md 顶部 2026-09-17 之前条目迁入
docs/archive/2026-09_status_history.md,仅保留指针
- D2 历史口径补齐为 2026-09-18 批次4后续专项清偿
- D4 标已清偿
- AGENTS.md / docs/archive/README.md 同步导航
D13 锁文件流程固化(镜像引入主体已清偿,仅剩锁文件落盘):
- 新增 deploy/generate_lockfiles.{sh,bat}:在 moldinsight conda
环境(仅项目依赖)执行 pip freeze --exclude pythonocc-core,
产出 deploy/requirements-{base,moldinsight}.lock.txt
- deploy/Dockerfile.moldinsight 注释改为指向生成脚本
- docs/OPERATIONS.md §2.1 增加完整流程说明
- tests/test_lockfile_generation.py 加锁文件存在性+体积契约;
tests/conftest.py 注册 --run-lockfile-check 选项,
默认 skip(仓库单测不阻塞),CI 镜像构建 job 显式启用 fail-fast
遗留:锁文件本身尚未落盘(本机Miniforge跨项目开发栈混装,
污染严重不能直接 pip freeze);待 CI / 生产首次构建时按流程落锁。
测试基线:126 passed, 9 skipped(默认4原有skip + D13新增5skip;
启用 --run-lockfile-check 时严格断言2项锁文件契约)
Co-Authored-By: Claude Code <noreply@anthropic.com>
106 lines
4.1 KiB
Python
106 lines
4.1 KiB
Python
"""D13:锁文件生成契约(部署侧硬性要求)
|
||
|
||
锁文件 deploy/requirements-{base,moldinsight}.lock.txt 的存在性 + 体积下限
|
||
是部署侧硬性要求:
|
||
|
||
- 锁文件必须在 moldinsight conda 环境构建成功后落盘(见 deploy/generate_lockfiles.sh/.bat)
|
||
- 锁文件必须以 git 跟踪方式提交,CI / 离线构建 / 生产复现部署才能直接 `pip install -r`
|
||
- 若 lock.txt 缺失或异常空(仅镜像元数据 < 5 行),说明构建流程未走 D13 流程
|
||
|
||
CI 门禁建议:
|
||
- 仓库侧默认 pytest(`pytest tests/ -q`)**不**强制这些断言——锁文件属"部署侧产物",
|
||
首次构建未完成时不应阻塞日常单测
|
||
- 部署侧 / CI 镜像构建 job 用 `--run-lockfile-check` 显式开启本套件(见 conftest.py)
|
||
"""
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
|
||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||
DEPLOY_DIR = REPO_ROOT / "deploy"
|
||
|
||
LOCK_FILES = [
|
||
DEPLOY_DIR / "requirements-base.lock.txt",
|
||
DEPLOY_DIR / "requirements-moldinsight.lock.txt",
|
||
]
|
||
|
||
GENERATOR_SCRIPTS = [
|
||
DEPLOY_DIR / "generate_lockfiles.sh",
|
||
DEPLOY_DIR / "generate_lockfiles.bat",
|
||
]
|
||
|
||
|
||
def pytest_collection_modifyitems(config, items):
|
||
"""仅在显式传入 --run-lockfile-check 时启用 D13 部署侧契约。"""
|
||
if not config.getoption("--run-lockfile-check", default=False):
|
||
skip_marker = pytest.mark.skip(
|
||
reason="D13 部署侧契约:默认 skip;CI 镜像构建 job 需传入 --run-lockfile-check 启用"
|
||
)
|
||
for item in items:
|
||
if "test_lockfile_generation" in item.nodeid:
|
||
item.add_marker(skip_marker)
|
||
|
||
|
||
def pytest_addoption(parser):
|
||
parser.addoption(
|
||
"--run-lockfile-check",
|
||
action="store_true",
|
||
default=False,
|
||
help="启用 D13 锁文件部署侧契约测试(CI 镜像构建 job 使用)",
|
||
)
|
||
|
||
|
||
def pytest_collection_modifyitems(config, items):
|
||
"""仅在显式传入 --run-lockfile-check 时启用 D13 部署侧契约。
|
||
|
||
说明:本钩子保留作为冗余保护(conftest.py 已注册同名钩子),
|
||
即便测试单独跑 pytest tests/test_lockfile_generation.py 也能正确跳过。
|
||
"""
|
||
if not config.getoption("--run-lockfile-check", default=False):
|
||
skip_marker = pytest.mark.skip(
|
||
reason="D13 部署侧契约:默认 skip;CI 镜像构建 job 需传入 --run-lockfile-check 启用"
|
||
)
|
||
for item in items:
|
||
item.add_marker(skip_marker)
|
||
|
||
|
||
def pytest_addoption(parser):
|
||
parser.addoption(
|
||
"--run-lockfile-check",
|
||
action="store_true",
|
||
default=False,
|
||
help="启用 D13 锁文件部署侧契约测试(CI 镜像构建 job 使用)",
|
||
)
|
||
|
||
|
||
@pytest.mark.parametrize("lock_path", LOCK_FILES)
|
||
def test_lockfile_exists_and_is_substantive(lock_path):
|
||
"""锁文件必须存在且非空(≥5 行 pip freeze 产物),否则部署侧契约缺失。"""
|
||
assert lock_path.exists(), (
|
||
f"缺少锁文件 {lock_path.relative_to(REPO_ROOT)};"
|
||
f"请在 moldinsight conda 环境执行 deploy/generate_lockfiles.sh/.bat 后提交"
|
||
)
|
||
line_count = sum(1 for _ in lock_path.open(encoding="utf-8") if _.strip())
|
||
assert line_count >= 5, (
|
||
f"锁文件 {lock_path.relative_to(REPO_ROOT)} 体积异常(仅 {line_count} 行非空行),"
|
||
"可能是构建流程未走通,请重新生成"
|
||
)
|
||
|
||
|
||
@pytest.mark.parametrize("script_path", GENERATOR_SCRIPTS)
|
||
def test_lockfile_generator_script_present(script_path):
|
||
"""锁文件生成脚本必须随仓库分发,否则新机器无法落锁。"""
|
||
assert script_path.exists(), (
|
||
f"缺少生成脚本 {script_path.relative_to(REPO_ROOT)};"
|
||
"D13 流程入口文件缺失"
|
||
)
|
||
|
||
|
||
def test_lockfile_dockerfile_comment_points_to_generator():
|
||
"""Dockerfile.moldinsight 必须明确指向锁文件生成脚本。"""
|
||
dockerfile = (DEPLOY_DIR / "Dockerfile.moldinsight").read_text(encoding="utf-8")
|
||
assert "generate_lockfiles" in dockerfile, (
|
||
"Dockerfile.moldinsight 应在注释中指向 deploy/generate_lockfiles.sh 以引导锁文件生成流程"
|
||
)
|