harness/ 模块是一个端到端功能测试框架,用于在真实的 Minecraft 服务器和客户端进程中验证 mod 的完整生命周期。与单元测试不同,这些测试会:
- 启动真实的 Minecraft 服务器/客户端进程
- 加载实际编译的 mod jar
- 驱动
/qb命令并验证文件系统和数据库状态 - 在分支声明支持的每个 Minecraft 版本和两个加载器(Fabric/NeoForge)上运行
- Java 21+(用于运行测试框架本身)
- 构建好的 mod jar:运行
./gradlew :fabric:remapJar :neoforge:remapJar - 足够的磁盘空间:首次运行会下载 Minecraft 服务器/客户端、库文件和资源(数 GB)
- 客户端测试需要显示环境:
- Windows/macOS:自动可用
- Linux 无头环境:需要 Xvfb(见下文)
- Java 21 分支:
1.20.2/1.20.3/1.20.5/1.21/1.21.4/1.21.5/1.21.6/1.21.7/1.21.10/1.21.11 - Java 25 分支:
26.1/26.2(需要设置JAVA25_HOME或-Dqbm.java25=<路径>)
./gradlew :quickbakcupmulti_reforged-harness:test这会运行 36 个纯逻辑单元测试(版本范围解析、报告格式、known-issues 匹配),不启动 Minecraft,1-2 秒完成。
最小测试(推荐首次运行):
./gradlew :quickbakcupmulti_reforged-harness:functionalTest \
-Pqbm.versions=1.21 \
-Pqbm.loaders=fabric \
-Pqbm.sides=server \
-Pqbm.scenarios=boot这会测试单个版本的服务器启动,用于验证环境配置正确。
完整矩阵:
./gradlew :quickbakcupmulti_reforged-harness:functionalTest这会对当前分支支持范围内的每个 Minecraft 版本,在 Fabric 和 NeoForge 上运行所有场景(启动、备份/还原、调度器、导出等)。
使用 Gradle 属性缩小测试范围:
| 属性 | 示例 | 说明 |
|---|---|---|
qbm.versions |
-Pqbm.versions=1.21,1.21.3 |
只测试指定的 MC 版本 |
qbm.loaders |
-Pqbm.loaders=fabric |
只测试指定的加载器 |
qbm.sides |
-Pqbm.sides=server |
只测试服务器端或客户端 |
qbm.scenarios |
-Pqbm.scenarios=boot,lifecycle |
只运行指定的场景 |
qbm.keepRunDirs |
-Pqbm.keepRunDirs=true |
保留运行目录以供调试 |
qbm.forceRobot |
-Pqbm.forceRobot=true |
强制走 Robot 键盘回退(仅本机;裸 Xvfb 下不可用) |
只测试服务器端(跳过客户端,不需要显示环境):
./gradlew :quickbakcupmulti_reforged-harness:functionalTest -Pqbm.sides=server调试特定版本的失败:
./gradlew :quickbakcupmulti_reforged-harness:functionalTest \
-Pqbm.versions=1.21.3 \
-Pqbm.loaders=neoforge \
-Pqbm.scenarios=lifecycle \
-Pqbm.keepRunDirs=true运行目录会保留在 harness/build/run/<version>-<loader>-<scenario>/,可以手动启动服务器调试。
客户端测试需要 X11 显示。使用 Xvfb 创建虚拟显示:
sudo apt-get install xvfb
xvfb-run -a --server-args="-screen 0 1280x1024x24" \
./gradlew :quickbakcupmulti_reforged-harness:functionalTest或设置环境变量:
export DISPLAY=:99
Xvfb :99 -screen 0 1280x1024x24 &
export QBM_HEADLESS_DISPLAY=1
./gradlew :quickbakcupmulti_reforged-harness:functionalTest每次运行后生成 build/compat-report/compatibility.md,包含:
| Version | Loader | Side | Scenario | Status |
|---------|--------|------|----------|--------|
| 1.21 | fabric | server | boot | ✅ PASSED |
| 1.21.1 | fabric | server | boot | ❌ FAILED: AssertionError: ... |
| 1.21.2 | neoforge | server | lifecycle | ⚠️ KNOWN_FAILURE (#123) |状态说明:
✅ PASSED:测试通过❌ FAILED:mod 在此版本上有 bug⚠️ KNOWN_FAILURE:已知问题,在.github/known-issues.json中标记🔧 ERROR:基础设施错误(下载失败、超时),不是 mod 的问题⏭️ SKIPPED:环境不支持(缺少 Java 25、无显示环境等)❗ UNEXPECTED_PASS:已标记的问题实际通过了,需要删除 known-issues 条目
- 测试日志:
build/compat-report/logs/<version>-<loader>-<scenario>/ - Minecraft 日志:
build/compat-report/logs/<version>-<loader>-<scenario>/boot-1.log
| 场景 | 覆盖内容 |
|---|---|
boot |
Mod 加载、Mixin 应用 |
lifecycle |
完整的备份/列举/还原循环 |
nested-world |
回归测试 #56(还原不创建嵌套 world/world) |
full-rotation |
回归测试 #55(完整备份轮转收敛) |
schedules |
回归测试 #54(重置一个调度器不影响其他) |
export-delete |
导出备份到独立副本并删除 |
| 场景 | 覆盖内容 |
|---|---|
menu |
客户端启动、Mixin 应用 |
world |
集成服务器启动、每个世界的备份存储初始化、通过聊天框驱动 /qb make |
restore |
完整的客户端驱动还原(/qb restore + /qb confirm),验证世界重建 |
如果某个版本有已知的 mod bug(不是测试框架的问题),在 .github/known-issues.json 中标记:
{
"issues": [
{
"branch": "1.21",
"version": "1.21.3",
"loader": "*",
"side": "*",
"scenario": "lifecycle",
"reason": "Mixin target moved in 1.21.3",
"issue": "#789"
}
]
}通配符:* 匹配任意值。例如 "version": "1.21.*" 匹配 1.21.1 / 1.21.2 / 1.21.3 等。
重要:如果标记的问题实际通过了,测试会失败(UNEXPECTED_PASS),提示删除过时的条目。这防止标记掩盖真正的回归。
GitHub Actions 工作流在 .github/workflows/functional-tests.yml:
- name: Run functional tests
run: |
xvfb-run -a --server-args="-screen 0 1280x1024x24" \
./gradlew :quickbakcupmulti_reforged-harness:functionalTest
env:
QBM_HEADLESS_DISPLAY: 1
QBM_HARNESS_CACHE: ~/.gradle/qbm-harness-cache
- name: Check gate
run: python3 .github/workflows/scripts/functional_gate.py build/compat-report矩阵生成:CI 从分支的 minecraft_supported_versions 动态生成矩阵:
./gradlew :quickbakcupmulti_reforged-harness:printMcMatrix输出 build/mc-matrix.json,工作流用它并行运行每个版本。
功能测试 workflow 跑在 ubuntu-latest + Xvfb 上,Windows 本机无法等价复现。
用 nektos/act 在 Docker Linux 引擎里跑同一份
.github/workflows/functional-tests.yml。
前置:Docker Desktop(Linux 引擎)、winget install nektos.act。
winget 装完可能要新开一个终端才有 act 的 PATH。先跑 setup,再跑
smoke / client / matrix。
pwsh ./scripts/act-ci.ps1 setup
pwsh ./scripts/act-ci.ps1 dry-run
pwsh ./scripts/act-ci.ps1 smoke # 1.21 Fabric 服务端 boot
pwsh ./scripts/act-ci.ps1 client # 1.21 Fabric 客户端 menu(Xvfb).actrc 使用 --network qbm-act 和 --bind。--bind 会把宿主机工作区
挂进容器,因此本机的 gradlew、build/、reports/、.act-reports/
可能被改写。smoke / client / matrix 开始时会删掉 reports/ 和
.act-reports/,避免旧报告让 gate 假绿。
不要对整个 support range 跑 act:那会下载数 GB 并持续很久。需要缩小范围时,
GitHub / act 都认同一组 inputs:versions、loaders、sides、scenarios。
原因:矩阵尝试测试 1.21.1,但分支只构建了 -mc1.21-fabric- jar。
解决:这是正常的 —— 分支的 minecraft_version=1.21 产出单个 jar,用于测试范围内的所有版本(1.21 / 1.21.1 / 1.21.2 / 1.21.3)。已在 Stage 0.1 修复。
解决:设置 JAVA25_HOME 环境变量或传递 -Dqbm.java25=/path/to/java。
原因:无显示环境(Linux 无头),或 GLFW 加载错误的原生库。
解决:
- 确认
DISPLAY已设置且 Xvfb 正在运行 - 设置
QBM_HEADLESS_DISPLAY=1 - 检查
build/compat-report/logs/<version>-fabric-client-<scenario>/client-1.log中的 GLFW 错误
检查:<runDir>/neoforge-installer.log
常见原因:
- 网络问题(Maven Central 不可达)— Stage 2 添加了重试
- JDK 版本不匹配 — Stage 2 修复了传递错误的
java
解决:从 .github/known-issues.json 中删除该条目,bug 已修复。
./gradlew :quickbakcupmulti_reforged-harness:printMcMatrix
cat build/mc-matrix.json输出示例(1.21 分支):
{
"branch": "1.21",
"versions": ["1.21", "1.21.1", "1.21.2", "1.21.3"],
"loaders": ["fabric", "neoforge"],
"java": 21
}默认 maxParallelForks=1(每次一个场景)。可以增加:
./gradlew :quickbakcupmulti_reforged-harness:functionalTest --max-workers=4但注意:每个场景占用 ~2GB 堆 + 整个服务器进程。
下载的 MC 服务器、客户端、资源缓存在 .gradle/qbm-harness-cache/(本地)或 $QBM_HARNESS_CACHE(CI)。clean 不会删除它。
手动清理:
rm -rf .gradle/qbm-harness-cache- 使用
-Pqbm.keepRunDirs=true保留运行目录 - 找到
harness/build/run/<version>-<loader>-<scenario>/ - 查看
config/QuickBackupMulti.json、world/、QuickBackupMulti/等 - 手动启动服务器调试:
cd harness/build/run/1.21-fabric-lifecycle java -jar server.jar nogui
- ❌ 不使用 vanilla launcher:直接从 Mojang/Fabric 元数据组装类路径
- ❌ 不模拟 Minecraft:启动真实的游戏进程
- ❌ 不使用 Minecraft 的 JUnit 钩子:这是一个外部黑盒测试
- ✅ 真实的游戏进程:
java -jar server.jar nogui或 Fabric 客户端启动器 - ✅ 标准输入命令(服务器):
/qb make "backup"写入 stdin - ✅ Robot 键盘注入(客户端):
java.awt.Robot在聊天框中输入/qb make clientprobe - ✅ 日志标记等待:
awaitLine("Make Backup thread close")扫描logs/latest.log - ✅ 文件系统和 H2 数据库断言:读取
world/、QuickBackupMulti/和.mv.db
- 每个场景一个新目录:避免泄漏状态,独立失败
- 内容寻址缓存:资源和库共享,避免重复下载 GB 数据
.part原子下载:中断的运行不会毒化缓存- xfail 系统:已知问题标记为
KNOWN_FAILURE,意外通过会失败(防止掩盖回归) - Robot 驱动客户端:真实的玩家输入路径,不是绕过 mod 的测试专用后门
- 在
ServerLifecycleTest.java或ClientLifecycleTest.java中添加@ParameterizedTest - 使用
runner.run(mc, loader, side, "scenario-name", () -> { ... }) - 在 lambda 中执行 provision → boot → 驱动命令 → 断言
- 运行
./gradlew :quickbakcupmulti_reforged-harness:functionalTest -Pqbm.scenarios=scenario-name验证
如果 mod 添加了新的英文日志标记(不依赖本地化),可以在测试中使用 awaitLine() / sawLine()。
禁止:依赖翻译的消息(tr("quickbackupmulti.backup.success")) —— 会在 lang=en_us 之外中断。
功能测试在 1.21 分支上全绿后,cherry-pick 到其他分支:
git checkout 1.20.5
git cherry-pick <commit-hash>
./gradlew :quickbakcupmulti_reforged-harness:functionalTest注意:26.1/26.2 分支使用 ci_build_task=build(不是 remapJar)和 Java 25,可能需要小调整。
成功运行:
[harness] branch 1.21 range [1.21, 1.21.4) -> versions [1.21, 1.21.1, 1.21.2, 1.21.3] on [fabric, neoforge]
[harness] using mod jar: build/libs/QuickBakcupMulti-Reforged-mc1.21-fabric-3.3.3+snapshot.jar
ServerLifecycleTest > 1.21 / fabric — boots with mixins applied PASSED
ServerLifecycleTest > 1.21 / fabric — make, list, restore round trip PASSED
ServerLifecycleTest > 1.21 / neoforge — boots with mixins applied PASSED
...
ClientLifecycleTest > 1.21 / Fabric client — restores a backup (client-driven) PASSED
Compatibility report: build/compat-report/compatibility.md
失败运行:
ServerLifecycleTest > 1.21.3 / fabric — make, list, restore round trip FAILED
org.opentest4j.AssertionFailedError: The restore did not revert level.dat to its backed-up size
Expected: 123456
Actual: 123461
--- last 40 lines of 1.21.3/fabric ---
[Server thread/INFO]: Make a temp backup success.
[Server thread/ERROR]: Failed to restore backup: ...
查看 build/compat-report/logs/1.21.3-fabric-lifecycle/boot-1.log 获取完整日志。
有问题或 bug?提交 issue 到 GitHub Issues,附上:
- 分支名称
- 完整的命令和输出
build/compat-report/compatibility.md- 相关的
build/compat-report/logs/日志