133 lines
8.3 KiB
Markdown
133 lines
8.3 KiB
Markdown
# ADR-0004: 测试载体与 CI(Test Carrier & CI)
|
||
|
||
## Status
|
||
Accepted(2026-09-12,solo 模式用户授权代决;迭代期可推翻重议)
|
||
|
||
## Date
|
||
2026-09-12
|
||
|
||
## Engine Compatibility
|
||
|
||
| Field | Value |
|
||
|-------|-------|
|
||
| **Engine** | Godot 4.7.2-stable |
|
||
| **Domain** | Core / Scripting(测试与工具链) |
|
||
| **Knowledge Risk** | LOW(--script/探针模式为 4.x 基线;godot-specialist 校验通过,含两条规则升格) |
|
||
| **References Consulted** | `docs/engine-reference/godot/`(四文件);godotengine/godot#6360(--script 模式不加载 autoload + 不注册全局标识符——官方立场) |
|
||
| **Post-Cutoff APIs Used** | None |
|
||
| **Verification Required** | None(切片 88/88 + 三探针已实证;CI 外部超时在正式 CI 接入时核对) |
|
||
|
||
## ADR Dependencies
|
||
|
||
| Field | Value |
|
||
|-------|-------|
|
||
| **Depends On** | ADR-0003(core 隔离规则是扫描域的输入) |
|
||
| **Enables** | 全部 GDD AC 的可执行化;正式 CI 接入 |
|
||
| **Blocks** | `/gate-check` 前置项之一(`/test-setup` 以本 ADR 为基线) |
|
||
| **Ordering Note** | gdUnit4 复议登记为 D2 OQ1 的关闭形态(推迟,非否决) |
|
||
|
||
## Context
|
||
|
||
### Problem Statement
|
||
D2 GDD 定下 `--headless --script` 冒烟载体(脚本 extends SceneTree、autoload 显式实例化),D2 OQ1 留下"gdUnit4 是否引入"待决;切片新增了场景级 UI 探针形态;项目级静态扫描域散落在各 GDD AC(AC-9/17/21/22/43 等)需要一处统一登记。
|
||
|
||
### Constraints
|
||
- headless 全套 < 30s 预算(technical-preferences)
|
||
- 致命类用例独立进程协议("调用 → 期望退出码" runner manifest,D2 AC-2b 起)
|
||
- 呈现层测试必须有承载(切片教训:已定稿 AC 全覆盖 core、呈现层零测试承载 → 缺陷 ①②④⑤ 漏网)
|
||
|
||
### Requirements
|
||
- 双模式边界明确:逻辑冒烟(无 autoload)vs 场景探针(完整项目引导)
|
||
- 静态扫描规则统一载体,覆盖 D2/A1–B4 各 AC 登记的扫描项
|
||
- CI 可接入且防挂死
|
||
|
||
## Decision
|
||
|
||
**双载体 + 统一静态扫描域;gdUnit4 推迟到正式 CI 期。**
|
||
|
||
**1. 逻辑冒烟(`--headless --script`)**:脚本 extends SceneTree 以获得 `quit(code)`;ConfigDB/RngService 由脚本显式实例化(引擎事实:--script 模式**不加载 autoload 且不注册全局标识符**——裸引用 = parse error 编译期即炸,GH #6360 官方确认)。Windows 用 console 版 exe 取 stdout。承载:D2/A1–B4 全部纯逻辑 AC + 致命类独立进程 manifest。
|
||
|
||
**2. 场景探针(headless 跑 .tscn 探针场景)**:以探针场景为运行目标(`godot --headless --path . res://smoke/<probe>.tscn`)走正常项目引导——autoload 在位、真实 SceneTree、`await process_frame` 有效。承载:呈现层场景级测试(切片三探针 ui_probe / ui_click_probe / ui_tune_probe 为先例)。
|
||
- **口径修正(specialist 校验)**:click_probe 走 `BoardView.click_cell` 是"真实翻译出口",**绕过 Viewport GUI 管线**(无命中测试/悬停/焦点)。表述定为"合成输入走真实翻译出口";真正的端到端输入测试 = `Input.parse_input_event()` / `Viewport.push_input` 带坐标——若 ADR-0003 的 _draw 迁移执行(Button 命中测试消失),push_input 探针从加分项变为**必需项**(登记入迁移工作项)。
|
||
|
||
**3. 项目级静态扫描域(统一登记,CI 与冒烟前置双跑)**:
|
||
|
||
| # | 规则 | 来源/动机 |
|
||
|---|---|---|
|
||
| S1 | 玩法代码禁 `randomize()`、自建 `RandomNumberGenerator` | D2 Rule 5(既有) |
|
||
| S2 | **禁全局随机函数 `randi()` / `randf()` / `randf_range()`**(全局 RNG 绕开 RngService——specialist 补登漏洞) | D2 Rule 5 补全 |
|
||
| S3 | core/ 禁 ConfigDB 外 data 表加载(`load(` / `preload(` / `ResourceLoader.load`;schema class_name 直引豁免) | D2 Rule 2(既有) |
|
||
| S4 | core/ 禁 `extends Control/Node2D`、禁 `.tscn` preload | D2 Rule 7(既有) |
|
||
| S5 | **core/ 禁裸引用 autoload 全局标识符**(`ConfigDB` / `RngService` 直接写 = script 模式 parse error)——构造注入 only | specialist 升格(切片已合规,规则防复发) |
|
||
| S6 | core↔core 信号连接只许方法 Callable(禁 lambda 捕获 self);发射者禁持有订阅者反向引用 | ADR-0001(防循环引用/生命周期延长) |
|
||
| S7 | B1 编排唯一性:B2/B3/B4 API 符号命中文件 ⊆ B1 文件集;A2/B3/B4 零相互直调 | B1 AC-43(既有) |
|
||
| S8 | 结算路径 `await` 零命中(B1 结算/编排文件集);字面量扫描 = warning 级 + 人工三要素复核(D2 AC-14 口径) | B1(既有) |
|
||
|
||
**4. CI 防挂死**:任何探针/冒烟调用旁必须配**外部 wall-clock 超时**(命令行 timeout)——引擎自身无死锁探测;探针内 30 帧停滞检测(click_probe 先例)是第一道、外部超时是最后防线。headless 全套 < 30s 预算不变。
|
||
|
||
**5. gdUnit4:推迟到正式 CI 期再议**(D2 OQ1 以"deferred"关闭)。它运行在项目模式(autoload 在位),与 SceneTree 冒烟机制正交,引入不废弃既有用例。
|
||
|
||
### Architecture Diagram
|
||
|
||
```
|
||
提交 → [静态扫描 S1–S8(秒级)] → [逻辑冒烟 --script(独立进程 manifest,<30s)]
|
||
→ [场景探针 ×N(headless + 外部超时)] → 全绿放行
|
||
```
|
||
|
||
### Key Interfaces
|
||
- 冒烟 runner manifest:条目 = "调用 → 期望退出码"(独立进程、游戏通道)——D2 AC-2b 协议延续
|
||
- 探针退出码:0 = PASS;非 0 = 失败类别;停滞 quit(2)
|
||
|
||
## Alternatives Considered
|
||
|
||
### Alternative 1: 立即引入 gdUnit4 并迁移全部冒烟用例
|
||
- **Pros**: 标准 assertions/参数化/报告;编辑器面板。
|
||
- **Cons**: 88 条用例迁移成本即刻发生;切片框架已验证且快(~3s);MVP 期无 CI 基建需求。
|
||
- **Rejection Reason**: 收益在 CI 期才兑现——推迟(非否决),D2 OQ1 关闭形态。
|
||
|
||
### Alternative 2: 仅保留逻辑冒烟、UI 靠人工
|
||
- **Cons**: 切片缺陷 ①②④⑤ 全部漏过既有 AC 的教训直接否定——呈现层无承载 = 呈现层缺陷零防线。
|
||
- **Rejection Reason**: 呈现层契约测试是 code-conventions 硬性要求。
|
||
|
||
## Consequences
|
||
|
||
### Positive
|
||
- 双模式边界以引擎事实固化(parse 期炸 vs 完整引导),不再靠约定俗成
|
||
- S2/S5/S6 三条 specialist 补登规则堵住静默翻车路径(全局随机绕开、裸引用编译炸、lambda 成环)
|
||
- 呈现层探针形态沉淀为项目标配(code-conventions 承载)
|
||
|
||
### Negative
|
||
- 静态扫描规则 8 条的维护成本(新规则须挂真实反面案例——code-conventions 纪律)
|
||
- "真实 UI 路径"表述收窄后,端到端输入覆盖存在已知缺口(_draw 迁移前可接受)
|
||
|
||
### Risks
|
||
- 未被停滞检测覆盖的死锁挂死 CI → 缓解:外部 wall-clock 超时为硬性要求
|
||
- 扫描规则漏报(启发式)→ 缓解:行为级硬证据并行(可回放断言、headless 实跑合成判定——D2 AC-9/10 模式)
|
||
|
||
## GDD Requirements Addressed
|
||
|
||
| GDD System | Requirement | How This ADR Addresses It |
|
||
|------------|-------------|--------------------------|
|
||
| d2-data-config.md | Rule 6 校验双通道;AC-2b 独立进程协议;OQ1 gdUnit4 | 冒烟框架定稿;manifest 延续;OQ1 关闭为 deferred |
|
||
| a1–b1 各 GDD | 各自静态扫描 AC(AC-9/17/18/19/21/22/43 模式) | S1–S8 统一登记为项目级域,各 AC 引用本 ADR |
|
||
| docs/code-conventions.md | 场景探针标配 / 提交前全量 | 双载体 + CI 链路承载 |
|
||
| b1-settlement.md | AC-43 编排唯一性单向扫描 | S7 载体 |
|
||
|
||
## Performance Implications
|
||
- **CPU**: 全套冒烟 ~3s(切片实测),预算 <30s 富余
|
||
- **Memory**: headless dummy server 极低
|
||
- **Load Time**: 探针场景引导一次性
|
||
- **Network**: 不适用
|
||
|
||
## Migration Plan
|
||
切片 smoke/ 目录即载体(零迁移);正式 CI 接入时补外部超时包装与 S1–S8 脚本化;`/test-setup` 以本 ADR 为基线脚手架。
|
||
|
||
## Validation Criteria
|
||
- 冒烟 + 探针全绿为提交门(code-conventions"提交前全量")
|
||
- 扫描域脚本化后:新增违规案例必须先补扫描规则再修码(缺陷-规范对照表纪律)
|
||
- `<30s` 预算在 CI 环境复核
|
||
|
||
## Related Decisions
|
||
- ADR-0001(S6 规则来源)、ADR-0003(S4/S5 规则来源、探针形态耦合)
|
||
- docs/code-conventions.md(缺陷-规范对照表)、各 GDD AC 前言(规范源)
|