Continue diff 测试用例三段式格式详解:为 streamDiff 算法编写黄金用例文件
【免费下载链接】continueopen-source coding agent项目地址: https://gitcode.com/GitHub_Trending/co/continue
本篇指南围绕 core/diff/test-examples/README.md 展开,它是 Continue 开源编码智能体代码编辑(diff)子系统中“流式 diff 算法”测试用例(fixture)文件的格式规范。读完本文,你将掌握 Continue 如何用「旧代码 / 新代码 / 期望 diff」三段式文件为streamDiff编写黄金用例,理解displayDiff的渲染约定、期望结果与myersDiff解耦的原因,以及“留空期望段 → 自动回填计算 diff → 人工校正”的用例生成工作流,从而能够独立为仓库新增或修正 diff 测试样本。
背景:测试用例文件在整个 diff 测试体系中的位置
在 Continue 仓库中,代码 diff 相关实现集中在 core/diff 目录,其中包括:
- streamDiff.ts —— 核心的流式 diff 算法实现,
streamDiff(oldLines, newLines)是一个AsyncGenerator<DiffLine>; - util.ts —— 提供
matchLine(模糊行匹配)、streamLines、generateLines等辅助函数; - streamDiff.vitest.ts —— 单元测试入口,其中面向“真实代码片段”的测试通过读取
test-examples目录下的用例文件来驱动; - test-examples —— 用例文件目录,目前包含 4 个
.diff样本与这份 README 规范。
可见 core/diff/test-examples/README.md 承担着“测试样本的书写规范”角色:它不描述算法本身,而是精确规定如何把一个 diff 测试场景表达成一个可被测试框架自动解析的文本文件。这是 Continue 对“用真实代码样本而非随机字符串验证 diff 正确性”所做的工程化约定。
用例文件格式规范:三段式结构与---分隔符
README 将每个测试用例定义为如下三段式结构:
<CODE BEFORE> --- <CODE AFTER> --- <EXPECTED DIFF>关键规则如下:
- 每个
.diff文件必须按顺序包含三段:变更前代码(CODE BEFORE)、变更后代码(CODE AFTER)、期望的 diff(EXPECTED DIFF); ---单独成行作为段分隔符(README 称之为 delimiter);- 分隔符两侧的空白(surrounding whitespace)会被修剪;
- 期望 diff 段可以通过
displayDiff函数生成(具体语义见下文“渲染约定”)。
从仓库实现看,streamDiff.vitest.ts 中的expectDiff(file)正是这一规范的唯一解析与执行方:
const [oldText, newText, expectedDiff] = testFileContents .split("\n---\n") .map((s) => s.replace(/^\n+/, "").trimEnd());也就是说,解析器先按字面\n---\n切分整个文件得到三段文本,再对每一段去除开头的空行并trimEnd()掉结尾空白——这与 README 中“surrounding whitespace will be trimmed”的说明一一对应。紧接着collectDiffs会把旧文本切行得到oldLines,把新文本经generateLines包装成行流后交给streamDiff,逐行收集真实计算结果。
需要注意的边界语义:解析只在整行恰好是
---时切分。因此如果被测代码中确实包含独立的---文本行,需要规避该写法,以免破坏三段切分。
真实用例逐个拆解:四种典型的 diff 场景
当前 test-examples 目录中已有 4 个真实样本,覆盖了从简单修改到复杂重排的不同编辑形态:
1.fastapi.py.diff:最简单的“改函数体 + 改 import”
fastapi.py.diff 演示的是最基础的场景:在from fastapi import FastAPI中追加HTTPException,并把/landing路由的return改成raise HTTPException(status_code=404, ...)。其期望 diff 段如下(已去除-前缀符号列的空格细节,完整内容见文件):
- from fastapi import FastAPI + from fastapi import FastAPI, HTTPException ... + raise HTTPException(status_code=404, detail="Page not found") - return {"message": "Welcome to the landing page"}注意期望段中每一行都带有+、-或空格前缀,+/-分别表示“新行/删除行”,而空格开头表示上下文行(未变化行)——这正是下文displayDiff约定生成的 unified diff 外观。
2.fastapi-tabs-vs-spaces.py.diff:缩进风格(Tab 与空格)差异的容忍测试
fastapi-tabs-vs-spaces.py.diff 中,变更前代码使用\t缩进,变更后代码改为 4 空格缩进。它被 streamDiff.vitest.ts 中名为 “tabs vs. spaces differences are ignored” 的测试引用,用于验证:在期望 diff 中,仅 tab/空格风格差异的行被算作 changed(输出-/+对),而语义等价的删除-新增不会被误并成多行改动。该用例与源码 util.ts 中“比较前先trimEnd()去除行尾空白”以及基于编辑距离的模糊匹配策略相呼应。
3.add-comments.py.diff:以注释插入为主的大批量新增
add-comments.py.diff 展示了“给每段代码加注释”这种典型 AI 编辑产物:变更后代码成段插入了注释行,同时删除了原本的return {"Hello": "World"}一行并替换成带注释的版本。对应测试名为 “FastAPI comments”(streamDiff.vitest.ts)。它的期望 diff 里可以看到+行与-行在“新增段落 + 被替换单行”上如何编排,对注释插入这类编辑的 hunk 形态很有参考价值。
4.mock-llm.ts.diff:TypeScript 中静态属性改造成 getter/setter
mock-llm.ts.diff 是跨语言样本:把一个Mock extends BaseLLM类中的static Completion = "Test Completion"属性改造成private static _completion+static get/set completion访问器,并同步更新两处引用。它验证了 diff 对“删除一段 + 插入一段结构上相似代码”的多行处理(如把static Completion换成get completion段后,旧行与新行分别以-/+输出)。对应测试名为 “Mock LLM example”(streamDiff.vitest.ts),也是全部样本中唯一非 Python 用例,说明该格式并不绑定特定语言。
期望 diff 的渲染约定:displayDiff做了什么
README 指出:“期望 diff 可以用displayDiff函数生成。”在测试文件 streamDiff.vitest.ts 与 streamDiff.vitest.ts 中定义了渲染规则:
const UNIFIED_DIFF_SYMBOLS = { same: "", new: "+", old: "-", }; function displayDiff(diff: DiffLine[]) { return diff .map(({ type, line }) => `${UNIFIED_DIFF_SYMBOLS[type]} ${line}`) .join("\n"); }要点:
- 这里所谓的“diff”指的是
streamDiff的输出——一个DiffLine[]数组,每个元素形如{ type, line },其中type取值来自DiffType = "new" | "old" | "same"(定义见 core/index.d.ts); - 渲染时按类型加前缀:
same行加一个空格、new行加+、old行加-,随后用换行拼接——结果就是上节样本中那种标准 unified diff 文本; displayDiff只负责把行序列“符号化”,不负责生成 diff 本身,生成交给被测的streamDiff。
为什么“显式期望”而不是直接对比myersDiff输出
README 特别强调了一个设计动机(原文要点):
“我们刻意把期望 diff 写成显式文本,而不是与
myersDiff的输出做比较,因为myersDiff的输出可能是不可达的(unattainable),或者并不完全是我们想要的结果。”
这句话背后对应仓库的两个事实:
- Continue 的
streamDiff是在线流式(streaming)算法——它按行异步消费新文件,尽量“所见即所得”地即时产出结果,且在空行、缩进、模糊匹配上有自身策略(见 streamDiff.ts 注释中的不变量与 util.ts 的matchLine启发式),并不是先求出全局最短编辑脚本再回放; - 经典的
myers-diff包(在测试中被称为myersDiff)虽然被引入用于对照验证基础场景(例如 streamDiff.vitest.ts 里成对的streamDiffs与myersDiffs断言),但它追求的是确定性的“编辑距离最优解”,与streamDiff面向编辑体验的启发式策略并不总是一致。
因此,如果直接用myersDiff的输出当“期望值”,一旦两者策略分歧,测试就会失败或掩盖真实期望。把期望 diff 固化成仓库内的黄金文本,等于把“streamDiff应该输出成什么样”的最终裁决权交给人审定的显式规格,而非另一个算法。这也解释了为何 README 的表述是 “We make this explicit ... in case the output from that is either unattainable or not exactly what we want.”
用例生成工作流:留空 → 自动回填 → 人工校正
对于很难手写出正确 unified diff 的新场景,README 给出了官方推荐的迭代流程(结合 streamDiff.vitest.ts 的实现可完整还原):
- 新建样本:在 core/diff/test-examples 下新建
<名字>.diff文件,先写好前两段(<CODE BEFORE>与<CODE AFTER>),把<EXPECTED DIFF>段留空; - 注册测试:在
streamDiff.vitest.ts的 describe 块中仿照现有写法添加一行,例如test("我的场景", async () => { await expectDiff("我的场景文件名"); });; - 运行测试:在仓库 core 目录下执行 vitest(如
npx vitest run diff/streamDiff.vitest.ts)。当检测到期望段为空时,expectDiff会打印 “Expected diff was empty. Writing computed diff to the test file”,并把displayDiff(streamDiffs)计算出的文本连同前两段一起回写进该.diff文件,随后抛出Error("Expected diff is empty")使测试失败(这正是为了让自动回填的中间产物不会被误当成通过); - 人工校正:检查回填的 diff 是否符合你对“理想输出”的预期——必要时手动调整增删行的顺序、范围与归属(例如把多余的整段重排修正成更小的局部修改),然后再运行测试直到通过。
这个工作流把“最容易出错的期望值书写”变成了“先让被测实现给出初稿、再由人来审定”,与 README 中“It is up to you to correct this to the expected diff”的说明完全一致。
编写新用例的实操建议
结合格式规范与streamDiff的实现特征,扩展测试样本时可以遵循以下检查清单:
- 尽量使用真实代码片段:样本是
fastapi.py、mock-llm.ts这类可读片段,比随机短串更能暴露注释插入、括号块移动、缩进风格切换等真实编辑问题; - 关注空白与缩进:需要明确该场景是“应当容忍的缩进变化”还是“应当显式展示的修改”。例如 util.ts 中,仅当行内容长度足够(
trim().length > 8)或后续行匹配持续宽松时,才允许把纯缩进差异当作 perfect match 保留旧行——写期望段时要与这些启发式保持一致; - 利用现有四类样本定位差异:修改 import(如
fastapi.py)、插入注释(如add-comments.py)、改变缩进风格(如fastapi-tabs-vs-spaces.py)、结构重构替换(如mock-llm.ts),新场景可以先对照最接近的样本起步; - 三段的空行处理:由于解析会对每段做“去开头空行 +
trimEnd()”,段首多余空行可省略,但段内有意保留的空行必须保留; - 每次改动后重新生成:若只是调整了新旧代码,直接清空期望段并重复上述“自动回填”流程即可,避免手写与算法输出偏差导致的无谓返工。
小结
core/diff/test-examples/README.md 用极简篇幅定义了一套高可用的 diff 测试用例格式:三段文本 +---分隔 + 显式期望 diff。配合 streamDiff.vitest.ts 中的解析与回填逻辑、streamDiff.ts 与 util.ts 中的算法实现,以及 core/index.d.ts 中DiffLine/DiffType的类型约定,你可以完整理解并复现 Continue 对“流式编辑 diff”正确性的验证方式。无论你是想给 Continue 提交新的 diff 回归用例,还是在自己的项目中借鉴“黄金用例 + 自动回填”的测试设计,这份规范与样本都提供了可直接照做的模板。
【免费下载链接】continueopen-source coding agent项目地址: https://gitcode.com/GitHub_Trending/co/continue
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考