ralph-flow-pi 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +428 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +56 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/prompts.d.ts +25 -0
- package/dist/commands/prompts.d.ts.map +1 -0
- package/dist/commands/prompts.js +249 -0
- package/dist/commands/prompts.js.map +1 -0
- package/dist/commands/tools.d.ts +47 -0
- package/dist/commands/tools.d.ts.map +1 -0
- package/dist/commands/tools.js +633 -0
- package/dist/commands/tools.js.map +1 -0
- package/dist/engine/check-bash.d.ts +121 -0
- package/dist/engine/check-bash.d.ts.map +1 -0
- package/dist/engine/check-bash.js +373 -0
- package/dist/engine/check-bash.js.map +1 -0
- package/dist/engine/check.d.ts +47 -0
- package/dist/engine/check.d.ts.map +1 -0
- package/dist/engine/check.js +298 -0
- package/dist/engine/check.js.map +1 -0
- package/dist/engine/core.d.ts +153 -0
- package/dist/engine/core.d.ts.map +1 -0
- package/dist/engine/core.js +1984 -0
- package/dist/engine/core.js.map +1 -0
- package/dist/engine/lock.d.ts +27 -0
- package/dist/engine/lock.d.ts.map +1 -0
- package/dist/engine/lock.js +121 -0
- package/dist/engine/lock.js.map +1 -0
- package/dist/engine/runner.d.ts +108 -0
- package/dist/engine/runner.d.ts.map +1 -0
- package/dist/engine/runner.js +510 -0
- package/dist/engine/runner.js.map +1 -0
- package/dist/engine/skills.d.ts +53 -0
- package/dist/engine/skills.d.ts.map +1 -0
- package/dist/engine/skills.js +109 -0
- package/dist/engine/skills.js.map +1 -0
- package/dist/engine/step-tools.d.ts +22 -0
- package/dist/engine/step-tools.d.ts.map +1 -0
- package/dist/engine/step-tools.js +45 -0
- package/dist/engine/step-tools.js.map +1 -0
- package/dist/engine/types.d.ts +136 -0
- package/dist/engine/types.d.ts.map +1 -0
- package/dist/engine/types.js +20 -0
- package/dist/engine/types.js.map +1 -0
- package/dist/headless.d.ts +57 -0
- package/dist/headless.d.ts.map +1 -0
- package/dist/headless.js +318 -0
- package/dist/headless.js.map +1 -0
- package/dist/pi/adapter.d.ts +135 -0
- package/dist/pi/adapter.d.ts.map +1 -0
- package/dist/pi/adapter.js +231 -0
- package/dist/pi/adapter.js.map +1 -0
- package/dist/pi/interactive.d.ts +28 -0
- package/dist/pi/interactive.d.ts.map +1 -0
- package/dist/pi/interactive.js +58 -0
- package/dist/pi/interactive.js.map +1 -0
- package/dist/pi/tui.d.ts +12 -0
- package/dist/pi/tui.d.ts.map +1 -0
- package/dist/pi/tui.js +12 -0
- package/dist/pi/tui.js.map +1 -0
- package/dist/tui/app.d.ts +25 -0
- package/dist/tui/app.d.ts.map +1 -0
- package/dist/tui/app.js +47 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/embed.d.ts +42 -0
- package/dist/tui/embed.d.ts.map +1 -0
- package/dist/tui/embed.js +38 -0
- package/dist/tui/embed.js.map +1 -0
- package/dist/tui/extension.d.ts +88 -0
- package/dist/tui/extension.d.ts.map +1 -0
- package/dist/tui/extension.js +114 -0
- package/dist/tui/extension.js.map +1 -0
- package/dist/tui/history-editor.d.ts +38 -0
- package/dist/tui/history-editor.d.ts.map +1 -0
- package/dist/tui/history-editor.js +55 -0
- package/dist/tui/history-editor.js.map +1 -0
- package/dist/tui/launcher.d.ts +24 -0
- package/dist/tui/launcher.d.ts.map +1 -0
- package/dist/tui/launcher.js +97 -0
- package/dist/tui/launcher.js.map +1 -0
- package/dist/tui/render.d.ts +87 -0
- package/dist/tui/render.d.ts.map +1 -0
- package/dist/tui/render.js +266 -0
- package/dist/tui/render.js.map +1 -0
- package/dist/tui/run-app.d.ts +49 -0
- package/dist/tui/run-app.d.ts.map +1 -0
- package/dist/tui/run-app.js +317 -0
- package/dist/tui/run-app.js.map +1 -0
- package/dist/tui/run-model.d.ts +162 -0
- package/dist/tui/run-model.d.ts.map +1 -0
- package/dist/tui/run-model.js +280 -0
- package/dist/tui/run-model.js.map +1 -0
- package/dist/tui/run-view.d.ts +71 -0
- package/dist/tui/run-view.d.ts.map +1 -0
- package/dist/tui/run-view.js +167 -0
- package/dist/tui/run-view.js.map +1 -0
- package/dist/tui/welcome-header.d.ts +40 -0
- package/dist/tui/welcome-header.d.ts.map +1 -0
- package/dist/tui/welcome-header.js +90 -0
- package/dist/tui/welcome-header.js.map +1 -0
- package/package.json +55 -0
- package/skills/c-to-rust-audit/SKILL.md +67 -0
- package/skills/c-to-rust-implement/SKILL.md +151 -0
- package/skills/c-to-rust-implement/references/c-to-rust-patterns.md +86 -0
- package/skills/c-to-rust-implement/references/conditional-compilation.md +47 -0
- package/skills/c-to-rust-implement/references/crate-reference.md +15 -0
- package/skills/c-to-rust-implement/references/error-strategies.md +80 -0
- package/skills/c-to-rust-implement/references/inline-asm.md +37 -0
- package/skills/c-to-rust-plan/SKILL.md +166 -0
- package/skills/c-to-rust-plan/references/detection-commands.md +66 -0
- package/skills/c-to-rust-test-gen/SKILL.md +130 -0
- package/skills/c-to-rust-test-gen/references/proptest-patterns.md +81 -0
- package/skills/c-to-rust-test-gen/references/test-porting.md +56 -0
- package/skills/c-to-rust-validate/SKILL.md +121 -0
- package/skills/everything2rust-audit/SKILL.md +69 -0
- package/skills/everything2rust-design/SKILL.md +121 -0
- package/skills/everything2rust-design/references/domain-playbooks.md +68 -0
- package/skills/everything2rust-design/references/paradigm-map.md +99 -0
- package/skills/everything2rust-implement/SKILL.md +101 -0
- package/skills/everything2rust-spec/SKILL.md +86 -0
- package/skills/everything2rust-spec/references/oracle-strategies.md +96 -0
- package/skills/everything2rust-survey/SKILL.md +99 -0
- package/skills/everything2rust-test-gen/SKILL.md +68 -0
- package/skills/everything2rust-test-gen/references/harness-patterns.md +186 -0
- package/skills/everything2rust-validate/SKILL.md +85 -0
- package/workflows/c-to-rust.yaml +202 -0
- package/workflows/everything2rust.yaml +259 -0
- package/workflows/loop.yaml +68 -0
- package/workflows/spec.yaml +183 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: everything2rust-implement
|
|
3
|
+
description: 按 plan.json 增量填充 Rust 桩,以行为契约为规格做 TDD 转绿。工程师式自治:编译器/测试报错即自愈信号,用 systematic-debugging 定位根因后修复,迭代到绿。在 everything2rust 工作流的 impl-core 和 impl-full 步骤触发。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
桩和验收测试已就位(来自 baseline)。你是工程师:读原实现理解**行为语义**(不是抄结构),按 design.md 的架构写惯用安全 Rust,让当前范围的测试全部转绿。没有"试 N 次就放弃"——持续迭代直到全绿。不要主动放弃任何能力。
|
|
7
|
+
|
|
8
|
+
与逐函数翻译的根本区别:你的规格是 behavior-spec.md 的契约和它物化成的测试,不是源函数签名。源代码是**理解行为的资料**——读它搞懂"为什么这个边界返回空而不是报错",然后用 design.md 定下的 Rust 架构自由地实现。
|
|
9
|
+
|
|
10
|
+
## 输入 / 输出
|
|
11
|
+
|
|
12
|
+
> `<产出目录>` = DO 提示词「产出目录」一节给出的路径(形如 `.ralph-flow/artifacts/<任务摘要>-<后缀>/`)。
|
|
13
|
+
|
|
14
|
+
- 输入:当前范围的增量(plan.json increments)、Rust 桩与测试、源项目、behavior-spec.md、test-map.json
|
|
15
|
+
- 输出:范围内测试全绿,clippy 干净,unsafe < 预算且每处有 SAFETY 注释,每增量一个 commit
|
|
16
|
+
|
|
17
|
+
## 范围
|
|
18
|
+
|
|
19
|
+
- **impl-core**:plan.json 中 `phase: core` 的增量(walking-skeleton + 核心域)
|
|
20
|
+
- **impl-full**:其余 `phase: full` 的增量
|
|
21
|
+
|
|
22
|
+
## 核心规则
|
|
23
|
+
|
|
24
|
+
- **测试即契约** — 当前范围 test-map.json 映射的测试全绿 = 该能力完成。测试失败时修实现,不修断言;确信断言本身与 behavior-spec 冲突时,先对照语料和源系统行为取证,再改断言并在 commit message 说明
|
|
25
|
+
- **walking-skeleton 优先**(impl-core 第一件事)— 先让 smoke_cmd 端到端跑通,再填充功能。骨架期暴露的选型问题(crate API 与预期不符、架构走不通)要立即处理:小问题直接修,推翻 ADR 级别的问题更新 decisions.md 和 plan.json 后再继续
|
|
26
|
+
- **行为偏差只在白名单** — 实现中发现"Rust 里这样做更自然但行为会变"时,查 parity_exceptions:在白名单里就做,不在就保持原行为(想加白名单不是本步骤的职权)
|
|
27
|
+
- **git 是检查点** — 每增量达到 exit_criteria 后 commit(`e2r-core: <increment>` / `e2r-full: <increment>`),大增量分批(`e2r-full: <increment> batch n/m`)
|
|
28
|
+
|
|
29
|
+
## 实现顺序
|
|
30
|
+
|
|
31
|
+
### 增量间
|
|
32
|
+
|
|
33
|
+
严格按 plan.json increments 顺序——排序已经编码了"高风险选型先验证"和依赖关系。
|
|
34
|
+
|
|
35
|
+
### 增量内
|
|
36
|
+
|
|
37
|
+
类型与错误 → 构造/初始化 → 无依赖的纯逻辑 → 有状态逻辑 → IO/边界集成。每写完一块就 `cargo build`,早发现早修。
|
|
38
|
+
|
|
39
|
+
### 逐能力对照(强制)
|
|
40
|
+
|
|
41
|
+
每完成一个增量,对照清单:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
增量 <id> 进度:
|
|
45
|
+
- [ ] 读源实现相关部分,理解每个能力的行为语义(含错误路径与边界)
|
|
46
|
+
- [ ] 按 design.md 架构实现(范式落差查 everything2rust-design/references/paradigm-map.md)
|
|
47
|
+
- [ ] 对照 test-map.json:本增量每个能力的测试全绿(golden 语料一个不剩)
|
|
48
|
+
- [ ] 确认实现非桩(不是空函数体、不是仅返回默认值/Ok(()))
|
|
49
|
+
- [ ] cargo build 通过;cargo clippy 本范围无 warning
|
|
50
|
+
- [ ] unsafe 在预算内且每处有 SAFETY 注释
|
|
51
|
+
- [ ] plan.json 中本增量能力 status 更新为 done
|
|
52
|
+
- [ ] git commit(e2r-core:/e2r-full: 前缀)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
没有能力可以跳过——要么实现,要么在 plan.json 里显式记录卡点原因(并且不输出 done)。
|
|
56
|
+
|
|
57
|
+
### 崩溃恢复(步骤开始时)
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
jq -r '.capabilities[] | select(.status=="done") | .id' <产出目录>/plan.json
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
以 `cargo test` 实际结果为准:标记 done 但测试失败的能力,重置 status 重做。
|
|
64
|
+
|
|
65
|
+
## 自愈循环 = systematic-debugging(不是机械计数)
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
cargo build 2>&1 → 编译器告诉你哪错 → 修 → 重来,直到 Finished
|
|
69
|
+
cargo test 2>&1 → 失败 → 复现 → 对照 behavior-spec 与源实现定位语义偏差
|
|
70
|
+
→ 改根因(不是改断言迁就)→ 重来,直到 ok
|
|
71
|
+
cargo clippy -- -D warnings → 修到干净
|
|
72
|
+
git commit
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**同一个错误反复出现**(试了几次没动)时停止盲改,切根因分析:
|
|
76
|
+
|
|
77
|
+
1. **重读源实现** — 对行为语义的理解很可能有偏差;golden 失败时把语料的 input 喂给原系统亲眼看输出
|
|
78
|
+
2. **检查语料/归一化** — 偶发失败或平台相关失败,可能是归一化规则漏了易变字段(对照 oracle-strategies 的归一化节),修 harness 而不是实现——但要先证明是 harness 问题
|
|
79
|
+
3. **检查所有权/并发模型** — 源系统的隐式共享(GC/单线程事件循环)在 Rust 需要显式结构;借用冲突是重新设计数据归属的信号,不是加 `clone()`/`unsafe` 的信号
|
|
80
|
+
4. **检查选型假设** — crate API 与预期不符:读编译器建议 + docs.rs 确认当前 API,精准打补丁;必要时 Cargo.toml 固定兼容版本;架构级不符 → 更新 ADR
|
|
81
|
+
|
|
82
|
+
## Unsafe 规则
|
|
83
|
+
|
|
84
|
+
度量口径全工作流统一:**cargo geiger** 本 crate 行的 Expressions `used/total` < plan.json `target.unsafe_budget_pct`(默认 10%)。
|
|
85
|
+
|
|
86
|
+
- 默认零 unsafe——绝大多数源语言模式都有安全 Rust 等价物(查 paradigm-map)
|
|
87
|
+
- unsafe 仅用于:FFI、平台原语(mmap/ioctl/SIMD)、自引用结构(优先 `Pin`)
|
|
88
|
+
- 每个 `unsafe {` 前一行 `// SAFETY:` 说明维持的不变性;块尽量小(1-3 行)
|
|
89
|
+
- 禁止用 `unsafe fn` 包装安全代码刷低比例
|
|
90
|
+
|
|
91
|
+
## 完成标准(每增量)
|
|
92
|
+
|
|
93
|
+
- 本增量能力的全部测试通过(golden 全过);实现非桩
|
|
94
|
+
- cargo build 通过;clippy 本范围无 warning;unsafe 在预算内
|
|
95
|
+
- plan.json status 已更新;有对应 commit
|
|
96
|
+
|
|
97
|
+
## 完成标准(impl-full 末,全项目)
|
|
98
|
+
|
|
99
|
+
- `cargo build --release` 通过;`cargo test --all` 全绿(无 FAILED、无 `#[ignore]` 逃逸——差分测试的 ignore 除外)
|
|
100
|
+
- `cargo clippy -- -D warnings` 无 error;无 `todo!()`/`unimplemented!()`/`dbg!()` 残留
|
|
101
|
+
- 全部能力 status=done;smoke_cmd 正常运行;geiger 在预算内
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: everything2rust-spec
|
|
3
|
+
description: 为源系统的每个能力定义行为契约(输入/输出/副作用/不变量/错误路径),选定预言策略,运行原系统采集 golden 语料。产出 behavior-spec.md 和 golden/ 语料库。在 everything2rust 工作流的 spec 步骤触发。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
你是规格工程师。上一步(survey)搞清了系统有哪些能力;这一步定义**每个能力必须保留的可观察行为**,并把判据物化成可执行的语料。行为契约是整个重构的宪法:design 以它为约束、test-gen 把它变成测试、audit 和 verify 拿它当判决标准。
|
|
7
|
+
|
|
8
|
+
关键立场:契约描述**外部可观察什么**,绝不描述**内部怎么实现**。"调用 parseConfig() 再调用 validate()"不是契约;"给定含未知字段的配置文件,警告到 stderr 并继续,退出码 0"才是。这条界线就是 Rust 侧获得架构自由的来源——everything2rust 不做函数一比一复刻,靠的就是把等价性锚定在行为而非结构上。
|
|
9
|
+
|
|
10
|
+
## 输入 / 输出
|
|
11
|
+
|
|
12
|
+
> `<产出目录>` = DO 提示词「产出目录」一节给出的路径(形如 `.ralph-flow/artifacts/<任务摘要>-<后缀>/`)。
|
|
13
|
+
|
|
14
|
+
- 输入:`<产出目录>/capabilities.md` + `system-map.md` + `oracle-evidence.md` + 源项目
|
|
15
|
+
- 输出:
|
|
16
|
+
- `<产出目录>/behavior-spec.md` — 逐能力的行为契约
|
|
17
|
+
- `<产出目录>/golden/` — 从原系统实际采集的输入→输出语料
|
|
18
|
+
|
|
19
|
+
## 预言策略(每个能力选一个主策略)
|
|
20
|
+
|
|
21
|
+
判据从哪来,按可靠性排序:
|
|
22
|
+
|
|
23
|
+
| 策略 | 适用 | 判据形态 |
|
|
24
|
+
|------|------|---------|
|
|
25
|
+
| **ported-tests** | 原系统有覆盖该能力的测试 | 移植原测试的断言语义 |
|
|
26
|
+
| **golden** | 能力行为确定、可离线重放 | 采集的输入→输出对,Rust 测试直接比对 |
|
|
27
|
+
| **differential** | 原系统可运行且行为复杂难穷举 | 测试时同时跑两个系统比对(原系统作为运行时预言) |
|
|
28
|
+
| **property** | 存在不变量(round-trip、幂等、守恒) | proptest 性质断言 |
|
|
29
|
+
| **checklist** | 自动判定不可行(渲染效果、手感、音频) | 人工核对清单,逐条写明验证方法 |
|
|
30
|
+
|
|
31
|
+
选择规则:能用上面的绝不用下面的;一个能力可以叠加次策略(golden 主 + property 辅很常见);checklist 是最后手段,每次使用都要写明**为何无法自动判定**。全项目 checklist 占比过高(>1/3)说明能力拆分有问题——把"渲染画面"类能力拆出可自动判定的确定性核心(布局计算、状态更新),checklist 只留纯感官部分。
|
|
32
|
+
|
|
33
|
+
各领域的采集技巧(CLI/HTTP/库/文件格式/游戏/GUI/不确定性行为的处理)见 **[references/oracle-strategies.md](references/oracle-strategies.md)**——动手采集前先读对应领域的小节。
|
|
34
|
+
|
|
35
|
+
## 执行流程
|
|
36
|
+
|
|
37
|
+
### 1. 逐能力写行为契约
|
|
38
|
+
|
|
39
|
+
behavior-spec.md 中每个能力一节:
|
|
40
|
+
|
|
41
|
+
```markdown
|
|
42
|
+
## cap-save-load — 存档与读档
|
|
43
|
+
- **预言策略**:golden(主)+ property(round-trip 辅)
|
|
44
|
+
- **输入空间**:任意合法游戏状态;损坏的存档文件;旧版本存档
|
|
45
|
+
- **输出/副作用**:写 saves/<slot>.dat;载入后状态逐字段恢复
|
|
46
|
+
- **错误路径**:文件损坏 → 弹提示不崩溃、不覆盖原文件;磁盘满 → 报错保留旧档
|
|
47
|
+
- **不变量**:save→load round-trip 恒等;load 不修改磁盘
|
|
48
|
+
- **边界**:空存档槽、并发保存、超长玩家名
|
|
49
|
+
- **golden 语料**:golden/save-load/(5 个状态样本 + 对应 .dat 文件)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
错误路径和边界不是可选项——它们是重构中最容易丢失的行为,也是 audit 步骤的重点核对对象。对照源码逐条确认,不要想当然。
|
|
53
|
+
|
|
54
|
+
### 2. 采集 golden 语料
|
|
55
|
+
|
|
56
|
+
对 golden/differential 策略的能力,**实际运行原系统**采集输入→输出对,存入 `golden/<cap-id>/`。每个语料目录带 `meta.json`:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"capability": "cap-save-load",
|
|
61
|
+
"captured_by": "cd /abs/source && npm run cli -- save --slot 1 < fixtures/state1.json",
|
|
62
|
+
"captured_at": "2026-07-02",
|
|
63
|
+
"source_version": "<git sha 或版本号>",
|
|
64
|
+
"cases": [{ "input": "cases/1/input.json", "expected": "cases/1/expected.dat", "notes": "" }]
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`captured_by` 必须是真实执行过的命令——语料要可复现、可被 CHECK 抽查。禁止手写"我认为的输出"充当语料;判据造假会让后面所有步骤在错误的靶子上收敛。
|
|
69
|
+
|
|
70
|
+
语料覆盖度:每个能力至少覆盖 happy path、一条错误路径、一个边界值。行为空间大的能力多采几组(解析器类建议 10+)。
|
|
71
|
+
|
|
72
|
+
### 3. 处理不确定性
|
|
73
|
+
|
|
74
|
+
时间戳、随机数、并发调度、浮点误差会让 golden 比对失效。对策记入契约的"归一化规则":注入固定 seed/冻结时钟重新采集;输出先归一化(剥离时间戳、排序无序集合)再比对;浮点用容差断言。归一化规则本身是契约的一部分——test-gen 会照着实现 harness。
|
|
75
|
+
|
|
76
|
+
### 4. 交叉核对
|
|
77
|
+
|
|
78
|
+
写完后过一遍:每个能力都有契约?每个 golden 能力都有语料目录?错误路径都对照过源码?原系统的已知 bug 按"bug 也是行为"处理——默认如实记录并保留等价行为,确要修复的不在这里决定,标注出来留给 design 的 parity_exceptions 裁决。
|
|
79
|
+
|
|
80
|
+
## 完成标准
|
|
81
|
+
|
|
82
|
+
- behavior-spec.md 覆盖 capabilities.md 全部能力,无 TBD
|
|
83
|
+
- 每个能力有预言策略、错误路径、边界、不变量
|
|
84
|
+
- golden/differential 能力在 golden/ 有带 meta.json 的语料,captured_by 可复现
|
|
85
|
+
- checklist 能力占比 < 1/3,每个都写明不可自动判定的原因
|
|
86
|
+
- 不确定性行为有归一化规则
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# 预言(Oracle)采集策略——按接口形态分节
|
|
2
|
+
|
|
3
|
+
## 目录
|
|
4
|
+
- 通用原则
|
|
5
|
+
- CLI 工具
|
|
6
|
+
- Web 服务 / 网络协议
|
|
7
|
+
- 库 / SDK
|
|
8
|
+
- 文件格式 / 数据管道
|
|
9
|
+
- 游戏
|
|
10
|
+
- GUI 应用
|
|
11
|
+
- 不确定性行为的归一化
|
|
12
|
+
|
|
13
|
+
## 通用原则
|
|
14
|
+
|
|
15
|
+
- 语料 = 真实运行原系统的产物。采集脚本本身留在 `golden/<cap-id>/capture.sh`(或 .py),语料过期时可重跑。
|
|
16
|
+
- 输入样本优先取自项目自带的 fixtures/examples/docs——它们是作者认可的典型用法。
|
|
17
|
+
- 每组语料记录:精确输入(含环境变量、工作目录)、精确输出(stdout/stderr 分开、退出码、产生的文件)。
|
|
18
|
+
|
|
19
|
+
## CLI 工具
|
|
20
|
+
|
|
21
|
+
采集:对每组参数/stdin 组合执行原程序,记录 stdout、stderr、exit code、生成的文件。
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
mkdir -p cases/1 && cd cases/1
|
|
25
|
+
echo '<stdin 内容>' > input.txt
|
|
26
|
+
<original-cmd> --flag value < input.txt > stdout.txt 2> stderr.txt; echo $? > exit_code.txt
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
覆盖:无参数(用法提示)、`--help`/`--version`、正常任务、非法参数、不存在的输入文件、空输入、超大输入。
|
|
30
|
+
Rust 侧 harness 用 `assert_cmd` 重放同样的调用并逐项比对。
|
|
31
|
+
|
|
32
|
+
## Web 服务 / 网络协议
|
|
33
|
+
|
|
34
|
+
采集:启动原服务,用 curl 对每条路由发请求,记录 status/headers/body。
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
curl -s -D headers.txt -o body.json -w '%{http_code}' http://localhost:PORT/path -X POST -d @req.json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
覆盖:每条路由的 2xx、4xx(缺字段/非法值/未授权)、边界(空列表、分页尾页)。
|
|
41
|
+
headers 只保留契约相关项(Content-Type、缓存策略),剥离 Date/Server 等噪音。
|
|
42
|
+
有外部依赖(下游服务、第三方 API)时录制其交互,Rust 测试用 wiremock 重放。
|
|
43
|
+
数据库状态是副作用的一部分:请求前后的关键表快照也算输出。
|
|
44
|
+
|
|
45
|
+
## 库 / SDK
|
|
46
|
+
|
|
47
|
+
采集:用**源语言**写 driver 脚本,调用公开 API,把输入输出序列化为 JSON。
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
// golden/cap-parse/capture.mjs
|
|
51
|
+
import { parse } from '../../src/index.js';
|
|
52
|
+
const cases = [/* 输入样本 */];
|
|
53
|
+
console.log(JSON.stringify(cases.map(input => {
|
|
54
|
+
try { return { input, output: parse(input) }; }
|
|
55
|
+
catch (e) { return { input, error: { type: e.constructor.name, message: e.message } }; }
|
|
56
|
+
}), null, 2));
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
异常也是输出——记录异常类型和关键信息(Rust 侧映射为 Err 变体)。
|
|
60
|
+
返回值含函数/闭包/复杂对象时,序列化其可观察投影(调用结果、关键字段),并在 meta.json 注明投影规则。
|
|
61
|
+
|
|
62
|
+
## 文件格式 / 数据管道
|
|
63
|
+
|
|
64
|
+
- **读**:收集真实样本文件(项目 fixtures、原系统生成的输出),语料 = 样本 + 原系统解析后的规范化 dump。
|
|
65
|
+
- **写**:原系统生成的文件即 expected;若格式含时间戳/随机 id,记录归一化规则。
|
|
66
|
+
- **互通是硬契约**:Rust 写的文件原系统能读、原系统写的 Rust 能读。采集双向样本。
|
|
67
|
+
- round-trip(parse→serialize→parse 恒等)是天然 property 策略,优先叠加。
|
|
68
|
+
|
|
69
|
+
## 游戏
|
|
70
|
+
|
|
71
|
+
核心手法:**把模拟(simulation)从呈现(presentation)中剥离**。
|
|
72
|
+
|
|
73
|
+
- **确定性模拟**:固定 seed + 固定 timestep 下,"初始状态 + 输入序列 → N 帧后的游戏状态"是确定的。采集:在原游戏中注入/录制输入序列,dump 关键状态(位置、血量、分数、库存)为 JSON。这是游戏逻辑的 golden 语料,覆盖游戏规则、物理、AI 决策。
|
|
74
|
+
- 原游戏没有 headless 模式时,写一个薄的 driver 直接调用其更新函数(跳过渲染),或在渲染入口打桩。改装原代码用于采集是允许的——改装只为观测,不改变逻辑,并在 meta.json 记录改了什么。
|
|
75
|
+
- **存档/配置/资产格式**:按"文件格式"一节处理,互通为硬契约。
|
|
76
|
+
- **呈现层(渲染、音频、手感)**:checklist 策略。清单写具体:"角色移动方向与方向键一致"、"受击有音效"、"60fps 下无可见卡顿",并注明验证方法(运行游戏人工核对/录屏)。
|
|
77
|
+
- **性能**:若原游戏有帧率目标,把它写进契约(如"1000 实体场景 ≥60fps"),Rust 侧用 criterion/手动计时验证。
|
|
78
|
+
|
|
79
|
+
## GUI 应用
|
|
80
|
+
|
|
81
|
+
- 把**应用逻辑**(文档模型、编辑操作、撤销栈、文件 IO)从视图剥离,逻辑部分按"库"策略采集:操作序列 → 模型状态。
|
|
82
|
+
- 视图部分 checklist:界面元素齐全、菜单项行为、快捷键映射。
|
|
83
|
+
- 文件格式互通同上(用户的旧文件必须能打开)。
|
|
84
|
+
|
|
85
|
+
## 不确定性行为的归一化
|
|
86
|
+
|
|
87
|
+
| 来源 | 对策 |
|
|
88
|
+
|------|------|
|
|
89
|
+
| 时间戳 | 冻结时钟采集(faketime/mock),或比对前剥离 |
|
|
90
|
+
| 随机数 | 固定 seed 采集;Rust 侧契约改为"接受 seed 参数"并记入 parity_exceptions 候选 |
|
|
91
|
+
| 无序集合 | 比对前排序 |
|
|
92
|
+
| 浮点 | 容差断言(相对误差 1e-9 起,按领域调整并记录理由) |
|
|
93
|
+
| 并发调度 | 契约只锁定顺序无关的最终状态;顺序敏感的行为单独写不变量 |
|
|
94
|
+
| 机器路径/主机名 | 采集时用固定沙箱路径,比对前替换为占位符 |
|
|
95
|
+
|
|
96
|
+
归一化规则写进 behavior-spec.md 对应能力的契约里,test-gen 按规则实现 harness 的 normalize 函数。
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: everything2rust-survey
|
|
3
|
+
description: 勘察任意语言的源项目:探测语言/领域/构建/测试/运行方式,安装源运行时让原系统真实跑起来,提取能力清单。产出 system-map.md、capabilities.md、oracle-evidence.md。在 everything2rust 工作流的 survey 步骤触发。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
你是接手陌生代码库的工程师。源项目可能是任何语言、任何领域——CLI 工具、库、Web 服务、游戏、GUI 应用、数据管道。目标:搞清楚它**是什么**、**能做什么**、**怎么跑起来**。后续所有步骤都建立在这份勘察之上:行为契约以能力清单为纲,验收以"原系统的真实行为"为预言(oracle)。
|
|
7
|
+
|
|
8
|
+
## 输入 / 输出
|
|
9
|
+
|
|
10
|
+
> `<产出目录>` = DO 提示词「产出目录」一节给出的路径(形如 `.ralph-flow/artifacts/<任务摘要>-<后缀>/`)。
|
|
11
|
+
|
|
12
|
+
- 输入:源项目路径(从用户任务描述中提取)
|
|
13
|
+
- 输出(均写入 `<产出目录>/`):
|
|
14
|
+
- `system-map.md` — 系统全貌:语言、领域、构建/测试/运行、外部接口、依赖
|
|
15
|
+
- `capabilities.md` — 能力清单:重构的等价单位
|
|
16
|
+
- `oracle-evidence.md` — 原系统可运行的实证(真实命令输出)
|
|
17
|
+
|
|
18
|
+
## 核心原则
|
|
19
|
+
|
|
20
|
+
- **先探测再假设** — 语言、目录布局、构建系统、测试框架都从项目实际情况探测,不套模板
|
|
21
|
+
- **让它跑起来是硬任务** — 原系统是行为等价的唯一权威预言;这一步要实际安装运行时、实际构建、实际运行、实际跑测试,把输出留证
|
|
22
|
+
- **能力是等价单位,不是函数** — 能力 = 一个外部可观察的行为闭环("解析配置文件并报错到 stderr"、"存档/读档"、"处理 POST /orders"),Rust 侧允许用完全不同的内部结构实现它
|
|
23
|
+
- **接口是能力的权威来源** — CLI 参数、HTTP 路由、公开 API、文件格式、UI 交互,每个外部接口背后都是能力
|
|
24
|
+
|
|
25
|
+
## 执行流程
|
|
26
|
+
|
|
27
|
+
### 1. 探测语言与领域
|
|
28
|
+
|
|
29
|
+
统计源文件扩展名分布、读 manifest(package.json / pyproject.toml / go.mod / pom.xml / *.csproj / Gemfile / CMakeLists.txt / …)、读 README。判定:
|
|
30
|
+
|
|
31
|
+
- **languages**:主语言 + 次要语言(构建脚本、嵌入的 DSL 不算主语言)
|
|
32
|
+
- **domain** ∈ {cli, library, web-service, game, gui, data-pipeline, systems}:看入口形态(main 循环?HTTP 监听?导出 API?渲染循环?)。混合领域取主形态并在 system-map 说明
|
|
33
|
+
|
|
34
|
+
### 2. 探测构建/测试/运行方式
|
|
35
|
+
|
|
36
|
+
从 manifest 的 scripts/targets、CI 配置(.github/workflows 等)、README 中提取:`build_cmd`、`test_cmd`、`run_cmd`。CI 配置往往是最可靠的来源——它是被机器验证过的。
|
|
37
|
+
|
|
38
|
+
### 3. 让原系统真实跑起来(产出 oracle-evidence.md)
|
|
39
|
+
|
|
40
|
+
按探测结果安装源语言运行时(node/python/go/jdk/dotnet/…,缺则装),然后**实际执行**:
|
|
41
|
+
|
|
42
|
+
1. 构建 → 记录输出尾部
|
|
43
|
+
2. 运行(用探测到的 run_cmd;服务类启动后 curl 健康检查;游戏/GUI 类尝试 headless/`--version`/`--help`,起不了窗口就记录到什么程度)
|
|
44
|
+
3. 跑测试套件 → 记录通过/失败统计
|
|
45
|
+
|
|
46
|
+
把每步的**真实命令和真实输出片段**写入 oracle-evidence.md。这不是形式主义——spec 步骤要靠运行原系统采集 golden 语料,这里验证的就是"采集通道是通的"。
|
|
47
|
+
|
|
48
|
+
原系统确实跑不起来时(缺私有依赖、平台不兼容、代码本身损坏):如实记录卡点和已尝试的方案,并明确替代预言来源(已有测试套件?文档?样例数据?),让 spec 步骤知道从哪取判据。不要伪造输出。
|
|
49
|
+
|
|
50
|
+
### 4. 提取外部接口清单
|
|
51
|
+
|
|
52
|
+
按领域用对应手段枚举:
|
|
53
|
+
|
|
54
|
+
| 领域 | 接口形态 | 探测手段 |
|
|
55
|
+
|------|---------|---------|
|
|
56
|
+
| cli | 子命令/flag/stdin/退出码 | 跑 `--help`、读 argparse/clap/commander 定义 |
|
|
57
|
+
| library | 公开 API | 读导出声明(export/pub/public)、类型定义、文档 |
|
|
58
|
+
| web-service | 路由/方法/请求响应体 | 读路由注册代码、OpenAPI 文件 |
|
|
59
|
+
| game | 输入操作/游戏规则/存档格式/资产 | 读输入处理、游戏状态更新逻辑、序列化代码 |
|
|
60
|
+
| gui | 界面操作/菜单/快捷键/文件格式 | 读事件处理器、菜单定义 |
|
|
61
|
+
| data-pipeline | 输入输出格式/CLI 参数/配置 | 读 IO 层、schema 定义 |
|
|
62
|
+
|
|
63
|
+
外加通用项:读写的文件格式、环境变量、配置文件、网络协议、数据库 schema、信号处理。
|
|
64
|
+
|
|
65
|
+
### 5. 归纳能力清单(capabilities.md)
|
|
66
|
+
|
|
67
|
+
把接口清单归纳为能力列表。每条能力:
|
|
68
|
+
|
|
69
|
+
```markdown
|
|
70
|
+
## cap-save-load — 存档与读档
|
|
71
|
+
- **行为**:游戏状态可序列化到存档文件,重新载入后完全恢复(关卡进度、物品、位置)
|
|
72
|
+
- **接口**:菜单"保存/载入";存档文件 `saves/*.dat`(自定义二进制格式)
|
|
73
|
+
- **源码**:src/save.ts, src/serialization.ts
|
|
74
|
+
- **依赖能力**:cap-game-state
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
粒度标尺:一条能力应当能用 1-5 个验收测试判定"做到了没有"。逐函数罗列太细(那是 c-to-rust 的做法),"实现整个游戏"太粗。典型项目 10-40 条。内部纯技术设施(日志、连接池)不单列能力——它们是实现细节,被外部行为间接覆盖。
|
|
78
|
+
|
|
79
|
+
### 6. 写出 system-map.md
|
|
80
|
+
|
|
81
|
+
```markdown
|
|
82
|
+
# System Map: <项目名>
|
|
83
|
+
## 概览 — 一段话:这是什么、给谁用、核心价值
|
|
84
|
+
## 语言与规模 — languages、代码行数分布、探测依据
|
|
85
|
+
## 领域判定 — domain 及理由
|
|
86
|
+
## 构建/测试/运行 — build_cmd / test_cmd / run_cmd(均已实际验证,见 oracle-evidence.md)
|
|
87
|
+
## 外部接口清单 — 第 4 步的完整结果
|
|
88
|
+
## 依赖清单 — 直接依赖及其角色(框架/引擎/工具库),标注哪些是架构级依赖(Rust 侧必须选型替代)
|
|
89
|
+
## 架构速写 — 主要模块和数据流(一段话 + 简单列表,供 design 参考,不必详尽)
|
|
90
|
+
## 风险与特殊性 — 动态特性重度使用、平台绑定、并发模型、性能敏感点、原系统已知 bug
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 完成标准
|
|
94
|
+
|
|
95
|
+
- system-map.md 各节完整,构建/测试/运行命令经过实际验证
|
|
96
|
+
- oracle-evidence.md 含真实命令输出;跑不起来时有卡点记录和替代预言来源
|
|
97
|
+
- capabilities.md 覆盖外部接口清单每一项,每条能力有行为描述 + 源码位置 + 接口
|
|
98
|
+
- 能力粒度符合标尺(每条可用 1-5 个验收测试判定)
|
|
99
|
+
- 三个文件写入 `<产出目录>/`
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: everything2rust-test-gen
|
|
3
|
+
description: 创建 everything2rust 迁移的 TDD 红阶段基线:按 plan.json 建 Rust 项目骨架,把行为契约变成验收测试(golden harness + 移植测试 + 属性测试),全部实现用 todo!() 占位。在 everything2rust 工作流的 baseline 步骤触发。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
严格 RED 阶段。设计已定(plan.json + design.md),行为契约和 golden 语料已就位(behavior-spec.md + golden/)。你的任务:把契约物化成**会失败的测试**,让后续 implement 步骤有明确的转绿目标。
|
|
7
|
+
|
|
8
|
+
测试是契约的可执行形式——它们的忠实度决定整个重构的质量上限。断言弱化(把"输出精确等于语料"降级为"不 panic")等于偷偷撕掉契约的一页,是本步骤最严重的失败模式。
|
|
9
|
+
|
|
10
|
+
## 输入 / 输出
|
|
11
|
+
|
|
12
|
+
> `<产出目录>` = DO 提示词「产出目录」一节给出的路径(形如 `.ralph-flow/artifacts/<任务摘要>-<后缀>/`)。
|
|
13
|
+
|
|
14
|
+
- 输入:`<产出目录>/plan.json` + `behavior-spec.md` + `golden/` + 源项目测试套件
|
|
15
|
+
- 输出:
|
|
16
|
+
- plan.json `target.output_dir` 下的 Cargo 项目:骨架 + 全桩 + tests/
|
|
17
|
+
- `<产出目录>/test-map.json` — 能力 id → 测试名列表
|
|
18
|
+
- 目标状态:`cargo build` 通过;`cargo test` 因 `todo!()` panic 而 FAILED(不是编译错误)
|
|
19
|
+
|
|
20
|
+
## 执行流程
|
|
21
|
+
|
|
22
|
+
### 1. 项目骨架
|
|
23
|
+
|
|
24
|
+
按 plan.json target 与 design.md 建项目:crate 形态(bin/lib/workspace)、模块结构、核心类型与错误枚举(**完整定义,非桩**——类型是测试能编译的前提)、Cargo.toml 依赖 = stack 选型 + dev-deps(proptest、rstest,按需 insta/assert_cmd/wiremock)。
|
|
25
|
+
|
|
26
|
+
### 2. 实现桩
|
|
27
|
+
|
|
28
|
+
design.md 草图中的每个公开函数/方法建桩:签名完整,函数体 `todo!("module::fn")`。桩的签名要经得起测试调用——写测试时发现签名不合理,直接改签名并同步 design.md(这是设计验证,不是失败)。
|
|
29
|
+
|
|
30
|
+
### 3. 验收测试(每能力至少 1 个,按预言策略写)
|
|
31
|
+
|
|
32
|
+
harness 代码模式见 **[references/harness-patterns.md](references/harness-patterns.md)**,动手前按策略读对应小节。
|
|
33
|
+
|
|
34
|
+
- **golden**:写数据驱动 harness,读 `golden/<cap-id>/` 的 cases 逐个断言。语料从 meta.json 声明的路径加载(复制进 `tests/golden/` 并保留 meta.json 亦可),**禁止把期望值硬编码进测试代码**——语料文件是判据的单一来源
|
|
35
|
+
- **ported-tests**:逐个移植原测试,断言语义精确保留,不合并不降级。原测试名可追溯(注释标注源文件)
|
|
36
|
+
- **property**:契约的不变量 → proptest(round-trip、幂等、守恒、单调性)
|
|
37
|
+
- **differential**:可行时写 `#[ignore]` 标注的差分测试(运行时调原系统比对),并确保其不阻塞 `cargo test` 默认运行——它们是 audit/verify 的加验手段
|
|
38
|
+
- **checklist**:不写自动测试;在 test-map.json 中标注 `"checklist"`,把 behavior-spec 的清单项复制到 `tests/CHECKLIST.md` 供 audit/verify 逐条核对
|
|
39
|
+
|
|
40
|
+
归一化规则(behavior-spec 中定义的时间戳剥离、排序、浮点容差)实现为 harness 的 `normalize` 函数,测试比对一律走它。
|
|
41
|
+
|
|
42
|
+
### 4. test-map.json
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"cap-save-load": { "tests": ["golden_save_load", "prop_save_load_roundtrip"], "kind": "auto" },
|
|
47
|
+
"cap-render": { "tests": [], "kind": "checklist", "checklist": "tests/CHECKLIST.md#cap-render" }
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
每个 plan.json 能力都要出现;kind=auto 的能力 tests 非空。这份映射是后续 impl/audit/verify 判断"哪个能力算完成"的索引。
|
|
52
|
+
|
|
53
|
+
### 5. 红状态验证
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cargo build # 必须 Finished——编译错误说明骨架/桩/测试有问题,修到能编
|
|
57
|
+
cargo test 2>&1 | tail -20 # 必须 FAILED,失败原因是 todo!() panic
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
逐项确认:FAILED 数量 ≈ 测试总数(个别纯类型测试可能已过);没有因语料路径错误而失败的测试(那是 harness bug,不是合法的红)。
|
|
61
|
+
|
|
62
|
+
## 完成标准
|
|
63
|
+
|
|
64
|
+
- Cargo 项目形态/依赖与 plan.json 一致,模块结构与 design.md 一致
|
|
65
|
+
- 每个能力在 test-map.json 有映射;auto 能力有 ≥1 个真实测试
|
|
66
|
+
- golden 测试从语料文件加载判据,断言忠实于 behavior-spec(未弱化)
|
|
67
|
+
- checklist 能力的清单落在 tests/CHECKLIST.md
|
|
68
|
+
- cargo build 通过;cargo test 因 todo!() 而 FAILED
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# 验收测试 Harness 模式
|
|
2
|
+
|
|
3
|
+
## 目录
|
|
4
|
+
- golden 数据驱动 harness
|
|
5
|
+
- CLI 重放(assert_cmd)
|
|
6
|
+
- HTTP 重放(axum oneshot)
|
|
7
|
+
- 库 API 语料重放
|
|
8
|
+
- 游戏模拟快照
|
|
9
|
+
- 属性测试(proptest)
|
|
10
|
+
- 差分测试(运行原系统)
|
|
11
|
+
- 归一化函数
|
|
12
|
+
|
|
13
|
+
## golden 数据驱动 harness
|
|
14
|
+
|
|
15
|
+
核心形态:一个测试函数遍历语料目录,每个 case 独立报告失败。
|
|
16
|
+
|
|
17
|
+
```rust
|
|
18
|
+
// tests/golden_common/mod.rs
|
|
19
|
+
use std::path::{Path, PathBuf};
|
|
20
|
+
|
|
21
|
+
pub struct GoldenCase {
|
|
22
|
+
pub dir: PathBuf,
|
|
23
|
+
pub input: String,
|
|
24
|
+
pub expected: String,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pub fn load_cases(cap_id: &str) -> Vec<GoldenCase> {
|
|
28
|
+
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/golden").join(cap_id);
|
|
29
|
+
let meta: serde_json::Value =
|
|
30
|
+
serde_json::from_str(&std::fs::read_to_string(root.join("meta.json")).unwrap()).unwrap();
|
|
31
|
+
meta["cases"].as_array().unwrap().iter().map(|c| GoldenCase {
|
|
32
|
+
dir: root.clone(),
|
|
33
|
+
input: std::fs::read_to_string(root.join(c["input"].as_str().unwrap())).unwrap(),
|
|
34
|
+
expected: std::fs::read_to_string(root.join(c["expected"].as_str().unwrap())).unwrap(),
|
|
35
|
+
}).collect()
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```rust
|
|
40
|
+
// tests/golden_parse.rs — 每个 case 失败时报出 case 路径
|
|
41
|
+
#[test]
|
|
42
|
+
fn golden_parse() {
|
|
43
|
+
for case in golden_common::load_cases("cap-parse") {
|
|
44
|
+
let actual = my_crate::parse(&case.input).unwrap();
|
|
45
|
+
assert_eq!(
|
|
46
|
+
normalize(&serde_json::to_string_pretty(&actual).unwrap()),
|
|
47
|
+
normalize(&case.expected),
|
|
48
|
+
"case: {}", case.dir.display()
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
case 数量多或想逐 case 独立显示时用 rstest 的 `#[files]` 或 libtest-mimic 生成动态测试。
|
|
55
|
+
|
|
56
|
+
## CLI 重放(assert_cmd)
|
|
57
|
+
|
|
58
|
+
```rust
|
|
59
|
+
use assert_cmd::Command;
|
|
60
|
+
|
|
61
|
+
#[test]
|
|
62
|
+
fn golden_cli_convert() {
|
|
63
|
+
for case in golden_common::load_cases("cap-convert") {
|
|
64
|
+
let assert = Command::cargo_bin("mytool").unwrap()
|
|
65
|
+
.args(case.args()) // meta.json 里记录的 args
|
|
66
|
+
.write_stdin(case.input.clone())
|
|
67
|
+
.assert();
|
|
68
|
+
let out = assert.get_output();
|
|
69
|
+
assert_eq!(out.status.code(), Some(case.exit_code()), "case: {}", case.dir.display());
|
|
70
|
+
assert_eq!(normalize(&String::from_utf8_lossy(&out.stdout)), normalize(&case.expected));
|
|
71
|
+
// stderr 语料存在时同样比对——stderr 格式也是契约
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## HTTP 重放(axum oneshot)
|
|
77
|
+
|
|
78
|
+
```rust
|
|
79
|
+
use tower::ServiceExt;
|
|
80
|
+
|
|
81
|
+
#[tokio::test]
|
|
82
|
+
async fn golden_orders_post() {
|
|
83
|
+
let app = my_service::build_app(test_state()).await;
|
|
84
|
+
for case in golden_common::load_cases("cap-orders-post") {
|
|
85
|
+
let req = case.to_http_request(); // meta.json: method/path/headers/body
|
|
86
|
+
let resp = app.clone().oneshot(req).await.unwrap();
|
|
87
|
+
assert_eq!(resp.status().as_u16(), case.expected_status());
|
|
88
|
+
let body = axum::body::to_bytes(resp.into_body(), usize::MAX).await.unwrap();
|
|
89
|
+
assert_eq!(normalize_json(&body), normalize_json(case.expected.as_bytes()));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
外部依赖:wiremock 起 mock server,语料 meta.json 中记录的下游交互配置成 stub。
|
|
95
|
+
|
|
96
|
+
## 库 API 语料重放
|
|
97
|
+
|
|
98
|
+
driver 采集的 JSON 语料(input/output/error 三态):
|
|
99
|
+
|
|
100
|
+
```rust
|
|
101
|
+
#[test]
|
|
102
|
+
fn golden_lib_parse() {
|
|
103
|
+
for case in load_json_cases("cap-parse") {
|
|
104
|
+
match (my_crate::parse(&case.input), case.expected) {
|
|
105
|
+
(Ok(v), Expected::Output(o)) => assert_eq!(to_value(v), o),
|
|
106
|
+
(Err(e), Expected::Error(spec)) => assert_eq!(error_kind(&e), spec.kind),
|
|
107
|
+
(got, want) => panic!("形态不匹配: got={got:?} want={want:?}"),
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
原系统异常 → Err 变体的映射表放在一处(error_kind 函数),保持全部测试一致。
|
|
114
|
+
|
|
115
|
+
## 游戏模拟快照
|
|
116
|
+
|
|
117
|
+
语料 = 初始状态 + 输入序列 + N 帧后的状态快照:
|
|
118
|
+
|
|
119
|
+
```rust
|
|
120
|
+
#[test]
|
|
121
|
+
fn golden_sim_combat() {
|
|
122
|
+
for case in load_sim_cases("cap-combat") {
|
|
123
|
+
let mut world = sim::World::from_snapshot(&case.initial);
|
|
124
|
+
let mut rng = sim::SeededRng::new(case.seed);
|
|
125
|
+
for frame_inputs in &case.input_frames {
|
|
126
|
+
world.step(frame_inputs, sim::FIXED_DT, &mut rng);
|
|
127
|
+
}
|
|
128
|
+
assert_eq!(world.observable_snapshot(), case.expected_snapshot,
|
|
129
|
+
"case: {}", case.dir.display());
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
前提(design 已保证):sim 无真实时钟、RNG 注入、固定 timestep。`observable_snapshot()` 只含契约关心的字段。浮点字段用容差比较实现 PartialEq 包装或逐字段 assert_relative_eq。
|
|
135
|
+
|
|
136
|
+
## 属性测试(proptest)
|
|
137
|
+
|
|
138
|
+
```rust
|
|
139
|
+
proptest! {
|
|
140
|
+
#[test]
|
|
141
|
+
fn prop_save_load_roundtrip(state in arb_game_state()) {
|
|
142
|
+
let bytes = save::serialize(&state)?;
|
|
143
|
+
let restored = save::deserialize(&bytes)?;
|
|
144
|
+
prop_assert_eq!(state, restored);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
不变量来源是 behavior-spec 的"不变量"字段。生成器(`arb_*`)覆盖契约的输入空间,包括边界(空、超长、Unicode)。
|
|
150
|
+
|
|
151
|
+
## 差分测试(运行原系统)
|
|
152
|
+
|
|
153
|
+
原系统可运行时的加验手段。标 `#[ignore]`,audit/verify 阶段用 `cargo test -- --ignored` 显式跑:
|
|
154
|
+
|
|
155
|
+
```rust
|
|
156
|
+
#[test]
|
|
157
|
+
#[ignore = "differential: 需要原系统运行时"]
|
|
158
|
+
fn diff_parse_random() {
|
|
159
|
+
for input in gen_random_inputs(200) {
|
|
160
|
+
let original = run_original(&["parse"], &input); // 调 plan.json source.run_cmd
|
|
161
|
+
let ours = run_ours(&["parse"], &input);
|
|
162
|
+
assert_eq!(normalize(&original), normalize(&ours), "input: {input:?}");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## 归一化函数
|
|
168
|
+
|
|
169
|
+
behavior-spec 的归一化规则集中实现在一个模块,全部 harness 共用:
|
|
170
|
+
|
|
171
|
+
```rust
|
|
172
|
+
pub fn normalize(s: &str) -> String {
|
|
173
|
+
let s = TIMESTAMP_RE.replace_all(s, "<TS>"); // 时间戳占位
|
|
174
|
+
let s = TMPPATH_RE.replace_all(&s, "<PATH>"); // 沙箱路径占位
|
|
175
|
+
s.trim_end().replace("\r\n", "\n") // 行尾统一
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
pub fn normalize_json(bytes: &[u8]) -> serde_json::Value {
|
|
179
|
+
let mut v: serde_json::Value = serde_json::from_slice(bytes).unwrap();
|
|
180
|
+
sort_arrays_marked_unordered(&mut v); // 契约标注无序的数组排序
|
|
181
|
+
strip_volatile_fields(&mut v); // 契约标注易变的字段剥离
|
|
182
|
+
v
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
剥离/占位的字段清单来自 behavior-spec——不要顺手多剥(会掩盖真实差异),也不要少剥(假失败消耗迭代次数)。
|