Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Keep the workflow's actions current, as reviewable pull requests.
#
# Every `uses:` in .github/workflows/ is pinned to a commit SHA with the tag spelled out in a
# comment, which is what makes an upstream moving a *tag* harmless — but a pin also stops
# receiving fixes, silently, until somebody looks. This file is that somebody: GitHub's own
# updater opens a PR for each new release, so the pin moves on a schedule and the diff (SHA plus
# the comment) is the review.
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: monthly
open-pull-requests-limit: 5
commit-message:
prefix: "ci"
labels:
- dependencies

# The test dependencies are pinned in tests/requirements-test.txt for reproducibility, and
# `pip` is what actually installs them into .testlibs; the language packages themselves are
# not declared with their own requirement files, so only this one matters.
- package-ecosystem: pip
directory: "/"
schedule:
interval: monthly
open-pull-requests-limit: 3
commit-message:
prefix: "test"
labels:
- dependencies
67 changes: 53 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
with:
python-version: "3.13"
cache: pip
cache-dependency-path: tests/requirements-test.txt
- name: Install ruff
run: python -m pip install "ruff==0.17.0"
- name: Lint
Expand All @@ -41,18 +43,51 @@ jobs:
matrix:
python-version: ["3.10", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: tests/requirements-test.txt
- name: Install test dependencies
run: python -m pip install --target .testlibs -r tests/requirements-test.txt
# Python 3.10 has no tomllib, so this matrix also exercises the plugin's regex reader
# for Forge/NeoForge metadata instead of only the parser every 3.11+ interpreter takes.
# The real-MCDR end-to-end is excluded here and covered by the matrix job below.
# The real-MCDR end-to-end is excluded here because it needs a real MCDR boot (about 2 m
# 20 s on its own) and is run by the `e2e` job below — not by the matrix job, which does
# not invoke pytest at all and therefore never ran these two cases.
- name: Run the suite
run: PYTHONPATH=.testlibs python -m pytest -m "not e2e"

e2e:
# The two end-to-end cases are in no other job: `unit` excludes them by marker, `mutation`
# only runs the files its selectors name, and `mcdr-matrix` drives its own scenario without
# pytest. So "827 passed, including 2 end-to-end" was a claim CI never checked. This job is
# that check — one real MCDR boot, the packed plugin, the whole flow.
name: end-to-end (real MCDR)
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
with:
python-version: "3.13"
cache: pip
cache-dependency-path: tests/requirements-test.txt
- name: Install test dependencies
run: python -m pip install --target .testlibs -r tests/requirements-test.txt
- name: Boot a real MCDR and drive the plugin through it
run: PYTHONPATH=.testlibs python -m pytest tests/test_e2e.py -q
- name: Upload the console when it fails
if: failure()
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: e2e-console
path: |
/tmp/pytest-of-*/**
/tmp/muc_e2e_*/**
if-no-files-found: ignore

python-floor:
# `ruff.toml` sets target-version = py38 because that is MCDR 2.13.0's floor, but a config
# file is not a check and no job above runs an interpreter that old. This job is the check:
Expand All @@ -61,8 +96,8 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
with:
python-version: "3.13"
- name: Every file still runs on the declared floor
Expand All @@ -75,10 +110,12 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
with:
python-version: "3.13"
cache: pip
cache-dependency-path: tests/requirements-test.txt
- name: Install test dependencies
run: python -m pip install --target .testlibs -r tests/requirements-test.txt
- name: Every mutation must be caught by at least one test
Expand All @@ -97,17 +134,19 @@ jobs:
matrix:
mcdr: ["2.13.0", "2.14.1", "2.15.0", "2.15.7", "2.16.0"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
with:
python-version: "3.13"
cache: pip
cache-dependency-path: tests/requirements-test.txt
- name: Install MCDR ${{ matrix.mcdr }} and pytest
run: python -m pip install --target .testlibs "mcdreforged==${{ matrix.mcdr }}" pytest
- name: Load the packed plugin, run a check, and drive every command
run: PYTHONPATH=.testlibs python tools/mcdr_matrix.py --current
- name: Upload the MCDR console when it fails
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: mcdr-${{ matrix.mcdr }}-console
path: /tmp/muc_matrix_work_*/**
Expand All @@ -121,8 +160,8 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
with:
python-version: "3.13"
- name: Pack twice, compare, and inspect
Expand Down
59 changes: 25 additions & 34 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,35 @@

---

## v1.6.1

这一版是一轮全代码审查的修复轮:下载台账的并发写入、频道开关失效、mods 目录读不到时的误报,
以及十几处「坏了但不出声」的路径。
## v1.6.2

### 修复

- **并发下载不再丢台账。** 下载台账以前由多个实例各自把内存快照整份写回,`!!muc download` 的两个后台线程、命令线程上的 `install` 与检查线程会互相覆盖(实测 9 条记录被清成 0 条),后果是「关服自动安装已批准的版本」静默失效。现在写入是进程级加锁的重读-合并-写回,JSON 的临时文件名也带上进程与线程号。
- **`check.include_beta` / `check.include_alpha` 之前完全不起作用**(频道过滤函数没有任何调用者),默认配置下 beta 会被当成可用更新推荐。现在两个开关生效;某个项目在所选频道里没有版本时会回落到更旧的频道,并在报告里说明这次回落。
- **`project-map.json` 存成 GBK / ANSI 不再让整次检查失败。** 以前 `UnicodeDecodeError` 会穿过映射表的错误处理:`!!muc status` 整页报错,一次检查以「check failed」收场、一条结论都不产出。
- **关掉 `sources.modrinth` 不再污染解析缓存。** 以前「没问过」会被写成「确认不存在」,打开 Modrinth 之后这些 jar 在缓存有效期内(默认 24 小时)一次名字搜索都不会发,永久显示为「认不出」。
- **下载失败不再先删掉旧版本。** 旧文件要等新文件就位之后才删,否则新版本 404 时新旧两份都没有,回滚点消失。
- **游戏版本投票只取下界。** 以前区间上界与下界等权计票、平局按字典序取最大者,猜错版本会让每个 Mod 都显示「无适配构建」。
- **`mods/` 目录读不到时不再报成「0 个 jar」。** 状态页写明目录与原因,检查直接跳过并说明原因——读不到的目录与空目录是两件事。
- **安装失败不再在 `mods/` 留下半截新 jar。** 改成同目录临时文件 + 原子替换;万一仍然留下东西,安装结果会点名那个文件。
- **上游整体不可用时不再对每个 Mod 逐个搜索**(以前 12 个 Mod 会发 48 次搜索请求)。
- 其余收紧:`resolve-cache.json` 里的坏记录不再让每次检查失败;`scan_mods` 在目录可 stat 不可列举时返回可解释的结果而不是抛异常;备份名判定改用 `fullmatch`;台账里的文件名做安全校验(不再接受 `..`);加密或损坏的 jar 不再打破 `scan_jar` 的「不抛异常」承诺;元数据解压加 4 MiB 上限;下载失败文案不再把不重试的失败说成「重试 N 次后失败」。
- 停服后在途的检查不再覆盖报告;`checker` 不再在关闭客户端之后继续发请求。

### 变更

- **`!!muc status` 改成两段式**:命令线程先回一句「正在读取」,扫描与渲染在后台线程完成后整屏给出——页面不再占用 MCDR 唯一的同步执行器线程;同一时间只允许一份状态页在生成。
- **数值选项超出范围会被钳制**并在控制台提示一次(范围写进 README 的配置表;只改内存,不回写管理员写的文件)。
- `!!muc` 别名下不再有写死 `!!modupdate` 的提示行;`cleanup.notice_confirm` / `command.delete.usage` / `command.delete.disabled_hint` 的占位符随之调整(翻译者注意)。
- 版本识别是从日志或 Mod 元数据推断出来的时候,状态页会多一行说明来源。
- 版本号 1.6.0 → 1.6.1。

### 内部整理

- 新增 `tools/mutation_check.py`:把每条守卫逐个改坏(在仓库的临时副本里改,工作树不受影响),对应的用例必须失败;没被抓住的守卫与「锚点挪走」分开报告。
- 新增 `tools/check_python_floor.py` 与 CI 的 `python-floor` 作业:每个文件都必须能以 3.8 解析、且不用更新的注解写法或标准库名字。
- 新增 CI 的 `lint` 与 `mutation` 作业;两个 workflow 都补上 `permissions`、`timeout-minutes` 与 `concurrency`。
- `README-dev.md` 引用的测量脚本(7 个)连同 `bench/README.md` 一起进仓库,并新增 `tests/test_documented_paths.py` 守卫文档里写出的仓库路径确实存在。
- 入口模块顶部按主题列出段落顺序与拆分计划(未搬迁)。
- **一个畸形 mod jar 不再让整次检查没有报告**:`depends.minecraft` 写成数字或对象(例如 `1.21` 忘了引号)时,以前会让检查整体失败、只剩一句 check failed。
- **下载请求也计入 `network.requests_per_minute`**:以前只有 JSON 请求限速,批量下载不受限。
- 响应体读到一半断连会重试;深嵌套响应抛出的 `RecursionError` 归类为上游错误,不再穿透检查流程。
- 安装成功后会删除已消费的下载文件并清扫下载目录,磁盘占用不再随更新次数单调增长。
- **并发写同一 JSON 文件不再失败**:按目标目录串行化写入并重试改名,缓存与批准状态不再静默丢失。
- 配置里写错类型会被点名并备份为 `config.json.broken.<时间戳>`,不再被 MCDR 静默重置为默认值。
- `ResolveCache` 只回写自己新增的记录,多个实例不再互相抹掉对方的解析结果。
- `!!muc list ²` 这类输入不再让命令静默失败;数值选项范围表补全(`network.retries`、`download.retries`、`network.cache.ttl_hours`、`report.reuse_report_minutes`)。
- 版本比较满足传递性,`is_prerelease` 与之一致:单字母后缀不再被当成预发布标记。
- `download all` 也遵守「同一文件不并发下载」;备份名改为原子抢占,不会再覆盖已有备份;`.part` 临时名唯一。
- 元数据解压改为分块读并设上限。
- i18n 不再依赖 Python 3.9+ 的 API,`language: auto` 在声明的 3.8 下限上不会退化成只有 `en_us`。
- 语言文件里让用户设置的键此前写的是缺少 `server.` 前缀的旧路径,照做的键会被 MCDR 丢掉;现已更正为 `server.mc_version`。
- `!!muc` 与 `!!modupdate` 两种别名下,屏内提示都指向用户实际输入的那个别名。
- `last_report.json` 改为原子写入;reload 后 `!!help` 的权限等级文案与实际一致。

### 工具与工程

- `tools/mcdr_matrix.py` 固定子进程编码并在中文终端下保住失败证据;`tools/probe_upstream.py` 接受参数、离线时给出结论而不是回溯。
- `tools/check_python_floor.py` 改为检查「模块 + 属性」对,`importlib.resources.files` 这类问题不会再漏过。
- CI 增加端到端作业、pip 缓存与固定 action 版本;补英文 README 与徽章。

### 验证

- 测试 **827 项**全部通过(其中 2 项是真实 MCDR 端到端)。
- 测试 **885 项**全部通过(含 2 项真实 MCDR 端到端)。
- 变异检查 **12 条全部被抓**。
- 真实 MCDR **2.13.0 / 2.14.1 / 2.15.0 / 2.15.7 / 2.16.0** 五版本各跑一遍完整流程,逐项一致。
- 产物可复现、换行归一、包内无注释与 docstring、包内 changelog 只留本节且与版本号一致。
- 真实 MCDR **2.13.0 / 2.14.1 / 2.15.0 / 2.15.7 / 2.16.0** 五版本矩阵通过,54 项跨版本判据一致。
- 产物重建与提交件逐字节一致;包内 changelog 只留本节且与版本号一致。
Loading
Loading