从《美国宪法》测试语料看 Harper 的快照回归测试机制
【免费下载链接】harperOffline, privacy-first grammar checker. Fast, open-source, Rust-powered项目地址: https://gitcode.com/GitHub_Trending/har/harper
Harper 是一款离线、隐私优先的 Rust 语法检查器。在harper-core/tests/text/目录下,The Constitution of the United States.md 是一份 732 行的《美国宪法》Markdown 全文副本,它不是给人阅读的文档,而是 Harper 词法/语法引擎的“黄金语料”:测试框架会对它逐行跑 lint 规则与词性标注,把结果固化为快照文件,任何规则或标注器行为变化都会让 CI 失败。读完本文,你会掌握这份语料文件的结构特征、它驱动的两条快照测试流水线(lint 快照与词性标注快照)、快照中的具体命中样例,以及如何查看和扩展这套测试资产。
1. 语料文件本身:一份带 Markdown 结构的宪法文本
1.1 文件定位与来源
该文件位于 宪法语料,首行是一条 HTML 注释,记录了语料的上游出处(一个公开的 USA-Constitution Markdown 仓库),随后才是正文。这个来源注释有两个作用:一是让维护者可以追溯文本版本;二是在词性标注快照中,整行注释会被标注器标记为Unlintable并整体跳过,不会参与 lint——从 词性标注快照 的第一行输出# Unlintable可以直接验证这一点。
1.2 Markdown 标记与原貌保留
文件在保留原始文本全部“不完美”特征的同时,套了一层简洁的 Markdown 标题结构:
- 标题层级:
# The Constitution Of The United States Of America(一级)、## Article. I.(二级)、### Section. 1.(三级)、#### SubSection. 1.(四级),完整覆盖原文的条/款/节层级; - 强调:开篇 "We the People" 使用粗体
**We the People**; - 有序列表:第一条第八款(Article I, Section 8)把国会 17 项权力(征税、借款、管理商务、铸币、设立邮局、宣战、维持军队与海军等)排成一个 1~17 的有序列表,位于 L207-L261;
- 原貌拼写全部保留:如
chuse、chusing、Behaviour、encreased、Controul、Rhode-Island、New-York、it's inspection Laws,以及 "Article. I." 这种带句点的编号习惯。
这些“保留原貌”恰恰是语料的测试价值所在:它们会在后面的 lint 快照中产生大量可复现的命中,成为引擎回归的锚点。
2. 语料如何进入快照测试流水线
Harper 有两套互补的测试机制,本语料属于其中快照机制:
- 精确计数测试:run_tests.rs 中的
create_test!宏针对tests/test_sources/下的文件,断言 lint 总数精确等于预期值(如create_test!(whack_bullets.md, 1, Dialect::American)),并额外断言每个 token 的 span 都能取到真实字符; - 快照测试:针对
tests/text/下的长文本语料(包括宪法),把全部lint 结果或词性标注整体序列化为快照文件比对。宪法语料走的是这条路径。
2.1 公共入口:snapshot.rs
snapshot.rs 提供两条测试线共用的基础设施:
- get_text_files():扫描
tests/text/顶层的.txt/.md文件(linters/、tagged/等子目录是快照输出目录,不会被当作输入重复处理); - try_get_dialect_override():从文件名主 stem 中解析方言覆盖,例如
Spell.US.md的.US会解析为美式方言;宪法文件名不含方言缩写,因此回退到按文档内容自动猜测、再兜底为美式; - tag_file():核心比对逻辑——重新计算快照内容,若与已提交的快照一致则通过;不一致或缺失时先写出新快照,再返回错误(
"Snapshot mismatches!"或"No snapshot!")。这意味着本地跑一次测试即可再生快照,而 CI 会因比对失败而变红,从而强制维护者审阅每一处行为变化; - snapshot_all_text_files():用 rayon 对全部语料文件并行处理,任一片段出错则整体 panic。
2.2 lint 快照:test_most_lints
linters.rs 的test_most_lints对每份语料执行以下流程:
let dict = FstDictionary::curated(); let document = Document::new_markdown_default(source, &dict); let mut linter = LintGroup::new_curated( dict, dialect_override.unwrap_or_else(|| { Dialect::try_guess_from_document(&document).unwrap_or(Dialect::American) }), ); let mut lints = linter.lint(&document); lints.sort_by(|a, b| { a.span.start.cmp(&b.span.start).then(a.span.end.cmp(&b.span.end)) });关键细节:
- 语料按Markdown解析(
Document::new_markdown_default),因此标题、粗体、有序列表的结构信息会进入 token 流,直接影响 lint 命中位置; - lint 按起始/结束偏移排序,保证快照顺序稳定可复现;
- 每条 lint 输出为
Lint:类型与优先级 +Message:(带行号与^~~下划线的源码上下文,由 print_error() 渲染,还会智能补一行前/后上下文以跨越句界)+ 可选的Suggest:替换建议。
输出写入 宪法 lint 快照,共 1851 行。
2.3 词性标注快照:test_pos_tagger
pos_tags.rs 的test_pos_tagger遍历文档的 fat string tokens,按 文档注释中定义的格式 输出:原文行以>开头,标签行以#开头,标签与对应单词逐字对齐;空格、换行忽略,段落分隔则输出两行空行。结果写入 宪法词性标注快照,共 1514 行。
3. Lint 快照剖析:语法检查器在宪法上的典型命中
这份快照相当于引擎的一次全量“体检记录”。以下按快照中实际出现的类型归纳(每条 lint 的优先级数字直接取自快照)。
3.1 标题与专有名词大写(Capitalization)
- 优先级 127 的标题 Title Case 检查命中一级标题
# The Constitution Of The United States Of America,建议改为The Constitution of the United States of America,见 快照 L1-L6; - 优先级 31 的专有名词检查多次命中正文中的
United States(如 L37-L44),提示“引用国家或国际组织时应按专有名词处理”; #### SubSection. 1.中的SubSection被词典规则建议改写为subsection。
3.2 长句可读性(Readability,优先级 127)
宪法长句密集,快照中数十条 Readability lint 给出精确字数:开篇 52 词、112 词、109 词、101 词,最极端的一条位于 快照 L879-L912——选举人团那一段被标记为204 词的单句。消息格式固定为 "This sentence is N words long.",并用下划线横跨整个句子范围(可跨行)。
3.3 拼写(Spelling,优先级 63)
语料刻意保留的历史拼写在这里批量命中,且每条都附三个候选替换(按编辑距离排序):
| 命中词 | 位置 | 候选建议 |
|---|---|---|
chuse/chusing | 第 64、72、103、125、383 行附近 | cause / chase / chose 等 |
Behaviour | 第 139 行 | Behavior / Behaviors |
encreased | 第 164 行 | increased / encased / entreated |
Controul | 第 315 行 | Control / Contour / Contrail |
Piracies/Offences | 第 229-230 行 | Pirates / Offenses 等 |
Habeas、facto | 第 271、274 行 | Haber's / fact 等 |
见例如 chusing 命中。注意Habeas、facto这类拉丁语术语被判为拼写问题——这正是快照测试的价值:它把“引擎认为错”的样本全部固化下来,供维护者决定是否扩充词典(词典资产位于 dictionary.dict,测试通过FstDictionary::curated()构建,从源码结构看该字典即 lint 判定词形的依据)。
3.4 风格、一致性与冗余
- Style(31 优先级):Oxford comma 检查在 "Times, Places and Manner"、"Trust or Profit"、"Trial, Judgment and Punishment" 等十余处建议插入逗号;
- Agreement:第 179 行
If he approve he shall sign it被标记主谓不一致,建议approves(快照 L479-L484);第 313 行it's inspection Laws被标记应使用所有格its; - Redundancy:
and also a President pro tempore建议简化为and; - Miscellaneous:
an uniform Rule的不定冠词被建议改为a(快照 L592-L599);overall连写复合词检查在 "like Authority" 附近命中。
3.5 暴露“过度纠错”的误报:快照的第二价值
快照中甚至保留了明显不合理的命中,例如 Typo 类规则把拉丁短语pro tempore建议拆成temp ore(快照 L250-L268),或建议Piracies改为Piracy's。这类记录并非文档缺陷,而是回归基线:一旦某个修复(比如把tempore加入词典、或抑制该规则在拉丁短语上的触发)改变了输出,测试会失败并强制维护者确认每一处差异,避免“修好一处、悄悄改变另一处”的隐性回归。
4. 词性标注快照:逐词对齐的标签输出
宪法词性标注快照 的前几行(L3-L16):
> The Constitution Of The United States Of America # Unlintable HeadingStart D NPr+ P D VP/J NPrPl/V3 P NPr+ > We the People of the United States , in Order to form a more perfect Union , # IPl+ D NPl/VB P D+ VP/J NPrPl/V3+ . NPr/J/R/P N🅪Sg/VB+ P N🅪Sg/VB D/P NPr/I/J/R/Dq NSg/VB/J+ NPr/VB/J+ .标签体系由 pos_tags.rs 的注释 完整定义,受 Penn Treebank 启发:N名词(NSg/NPl单复数、NPr专有、N🅪Sg可数质量名词、Nᴹ质量名词)、I代词、V动词(VB原型、VXB助动词、VPp过去分词等)、J形容词、R副词、C连词、D限定词、P介词、Dq量化限定等;/表示词性不确定(如NPrPl/V3既可作复数专有名词也可作第三人称单数动词);+表示名词短语成员关系。标点为.、数字为#,Markdown 标题起始位置会额外出现HeadingStarttoken(如 L19-L24 中Article前的标记)。
由于宪法原文保留了 18 世纪语法(he approve、shall be a Senator等),标签快照同时覆盖了动词屈折、代词单复数、专名/通名歧义等大量边界情形,是标注器(词块切分与词性标注模型资产位于 harper-brill/ 等模块,从仓库结构看由该包提供)最重要的回归语料之一。
5. 查看与扩展指南(只读参考)
- 查看语料与其快照:语料在 tests/text/,配套快照为 lint 版 .snap.yml 与 词性标注版;
- 运行方式:在仓库根目录执行
cargo test -p harper-core --test linters与cargo test -p harper-core --test pos_tags分别运行两条快照线(测试内部并行处理全部语料);cargo test -p harper-core --test run_tests运行精确计数线; - 新增语料的约定(来自 linters.rs 与 pos_tags.rs 的文件头注释):把新文档放入
tests/text/后运行测试,即会在tests/text/linters与tests/text/tagged自动创建同名快照;若文件名 stem 含.US等方言缩写可覆盖自动猜测的方言; - 注意事项:快照文件一旦落后于引擎行为,测试会失败并在本地写出新内容,需人工审阅差异;
tests/text/下的子目录(linters/、tagged/、Difficult sentences等语料)不会被当作输入重复扫描。
小结
这份《美国宪法》Markdown 语料是 Harper 引擎的“长期压力样本”:它的 Markdown 结构验证解析器,它的长句、古旧拼写、专名与代词歧义全面覆盖了 Capitalization、Readability、Spelling、Style、Agreement、Redundancy 等 lint 家族,它的每一处词性标注都被逐字对齐固化。快照机制(snapshot.rs+linters.rs+pos_tags.rs)保证了引擎的每个行为变化都必须显式通过审阅——这正是 Harper 能在保持离线、零配置的前提下持续演进规则集的测试基石。
【免费下载链接】harperOffline, privacy-first grammar checker. Fast, open-source, Rust-powered项目地址: https://gitcode.com/GitHub_Trending/har/harper
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考