Slang 编译器白盒特征化测试套件:coverage 测试树的方法论与实践指南

Slang 编译器白盒特征化测试套件:coverage 测试树的方法论与实践指南 Slang 编译器白盒特征化测试套件coverage 测试树的方法论与实践指南【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang本文讲解 Slangshader 编译器项目中第三棵自动生成测试树docs/generated/tests/coverage/的完整方法论它如何与conformance/、design/两棵姊妹树分工如何通过白盒特征化测试white-box characterization tests直接瞄准覆盖率报告中的缺口分支以及每个测试文件必须遵守的元数据契约、gap 分流triage规则与可复现的生成工作流。读完本文你将掌握如何判断一个未覆盖分支是否值得写测试、如何用//META块正确声明特征化测试、如何区分待验证行为与编译器缺陷以及整套测试树是如何由whitebox-coverage.js自动生成的。一、三棵生成测试树的定位coverage 是第三棵且刻意不同docs/generated/tests/coverage/METHODOLOGY.md开篇即声明这是第三个生成测试树与conformance/和design/是并列关系但定位刻意不同。三棵树的断言对象与失败含义有严格分工测试树断言对象失败的含义conformance/语言参考文档docs/language-reference/规范与编译器之间出现漂移spec-vs-compiler driftdesign/生成的架构设计文档docs/generated/design/已固化的行为发生回归behavioural regressioncoverage/其他测试均未触及代码的当前可观测行为代码发生了变化由人工分流triage判定是修复还是回归这一分工在_meta/CAMPAIGN.md的来源权威层级Source-of-truth hierarchy中有更完整的背景conformance/锚定人工编写的权威规范design/锚定从源码反推的设计文档而coverage/锚定的唯一权威是编译器源码本身。这个树存在的原因只有一个以文档为锚的生成流程doc-anchored flow在达到目标覆盖率之前就陷入平台期——因为文档并没有描述每一个可达分支reachable branch。coverage 树的目标就是直接瞄准这些分支。它是整个测试体系中唯一允许阅读覆盖率报告和编译器源码来选择测试的地方这与_expand.md中的 hard rule扩展流程严禁获取任何源码行级信息恰好相反除本树之外该规则在其他地方依然生效。二、什么是白盒特征化测试coverage 树的测试被称为白盒特征化测试。两个关键词缺一不可白盒white-box测试的选择依据是覆盖率缺口未覆盖的编译器代码允许直接阅读编译器源码来设计输入特征化characterization测试断言的是slangc 当前实际做了什么observed current behaviour而不是规范要求它应该做什么what the spec says it should do。这意味着 coverage 树的测试不是规范测试而是行为快照测试。一个典型例子是coverage/parser/operator-overload-plus-lowers.slang它通过//TEST:INTERPRET(filecheckCHECK):让解释器执行用户自定义的operator断言a b的结果是 5——这个断言只在运算符声明被正确解析并绑定为时才能成立但它不声称这是任何规范要求的行为只记录编译器确实如此。三、首要风险不要把 bug 固化为预期行为由于特征化测试钉死的是当前输出一个针对 buggy 分支编写的测试会把 bug 永久锁进测试套件。METHODOLOGY.md给出了强制的缓解措施钉死真实观测输出运行 slangc逐字复制真实输出——绝不允许凭空发明never invent无法确认正确性时必须打标签如果找不到规范、或无法确认行为是否正确必须给测试加上//META: characterization-unverifiedtrue交由人工/规范评审复审。只有带上这个标签钉死未验证行为才是被允许的崩溃/中止/内部错误/畸形输出是一个发现finding永远不是一个测试这类结果必须按_common.md中报告疑似编译器缺陷的约定归档到_meta/findings/下而不是改写成通过测试语言参考文档确实覆盖的行为优先写到conformance/coverage 树只服务于那些真正无文档可依的可达代码。在仓库中可以看到这套机制的实际运转docs/generated/tests/_meta/findings/目录下存有parser-magic-type-modifier-on-user-struct-sigsegv.yaml__magic_type修饰符导致 SIGSEGV记为一个 finding 而非测试、spirv-asm-extra-operand-on-zero-operand-op-internal-error.yaml、torch-autobind-unmappable-param-sigsegv.yaml等数十份结构化的缺陷发现记录。而coverage/parser/gpu-foreach-statement.slang则是带标签钉死的正面例子它的 META 块同时声明了//META: intentcharacterization、//META: coverssource/slang/slang-parser.cpp与//META: characterization-unverifiedtrue——因为编译产出的kernelCall_0_wrapper符号并未由编译器在生成代码中定义测试明确注释这是观测到的行为不断言它是否是有意的契约。四、先分流triage只瞄准可达缺口绝大多数未覆盖行不值得或不可能去写测试。写测试之前必须对每个缺口分类缺口类别处理方式可从 slangc / slangi CLI 到达瞄准它——这正是本套件的工作受运行环境门槛限制需要 GPU / DXC / nvrtc / Metal 工具链用//META: requires-tool...写测试CI 负责验证本地运行报告ignored防御性 / 不可达对不可能状态的断言、穷尽 switch 上的default:不要瞄准——在 bundle 的 README## Unreachable gaps中记录原因死代码没有任何入口能到达的调用者不要瞄准——这是建议删除的候选 finding而不是测试对象分流映射什么未覆盖、以及为什么未覆盖本身就是一份交付物——它揭示了真实的可达覆盖天花板the real reachable ceiling。五、每个测试的契约per-test contractcoverage 树中的每个.slang或 CLI / 单元 harness测试必须满足驱动真实输入经过build/.../slangc或slangi并且编译/运行干净通过钉死观测输出使用filecheck/filecheck-buffer或diag匹配器内容逐字来自真实输出匹配策略是对区分性 token 严格、对次要的 id/寄存器宽松携带标准//META块外加本树特有的三个要求//META: intentcharacterization声明这是特征化测试//META: coverssource/slang/file.cpp声明白盒目标——本测试针对的源码文件/区域它取代了doc_ref要求此处不适用//META: characterization-unverifiedtrue当正确性未经确认时。豁免项doc_ref在本树不要求本树豁免于 emission fan-out gate发散门控和 doc-anchor lint。本树自己的 lint 要求是必须有intentcharacterization、必须有covers目标、以及至少一个匹配器matcher。以coverage/parser/decl-not-allowed-in-statement.slang这类诊断路径测试为例错误路径使用//DIAGNOSTIC_TEST即使本地没有 FileCheck 也能验证正向语法路径使用//TEST:INTERPRET解释器验证值或//TEST:SIMPLE(filecheck...)对发射的 HLSL/SPIR-V 汇编做检查。六、如何生成whitebox-coverage 工作流产生这些 bundle 的扫描流程是_meta/workflows/whitebox-coverage.js。该文件在语料库中保留目的是让本树可复现而不只是被描述——重新运行它即可重新生成这些 bundle。其工作方式硬编码可达源区簇reachable source-area clusters每个簇对应一个 bundle由缺口分流gap triage选定并附带触发该簇的行覆盖率数字每个簇运行一个 agentparallel()并行调度所有 agent 共享同一个 PROMPT 模板每个 agent 的产出通过RESULT_SCHEMA结构化校验要求包含bundle、new_tests并报告diagnostic_tests、unverified、findings、finding_ids、unreachable_noted等计数。工作流中声明的 11 个簇即对应仓库中的 11 个 bundle 目录每个簇标注了目标源文件与当时的行覆盖率例如bundle目标源文件覆盖率可达输入提示节选check-declsource/slang/slang-check-decl.cpp (63%)声明形式、修饰符、可见性、初始化器、泛型/extension/typedef/enum/property/subscript 声明及文档化的错误路径parsersource/slang/slang-parser.cpp (68%)语法边界情况属性语法、泛型实参语法、运算符声明、layout 限定符、表达式语句形式、解析错误恢复emitslang-emit-spirv.cpp (60%)、slang-emit-glsl.cpp (50%)、slang-emit-c-like.cpp (62%)较不常见的 intrinsics、控制流形态、struct/array 返回、矩阵操作发射到 spirv-asm/glsl/cpplegalizeslang-ir-glsl-legalize.cpp (63%)、slang-ir-legalize-types.cpp (48%)、slang-ir-legalize-varying-params.cpp (61%)类型/变长参数 legalization、varying in/out struct 展平、系统值语义、跨 spirv/glsl 的入口参数形态torchslang-ir-pytorch-cpp-binding.cpp (15%)-target torch/ TorchTensor /[CudaKernel]/[TorchEntryPoint]绑定发射同时工作流明确排除了三类无法用.slang测试的簇slang-emit-llvmLLVM 后端被禁用、reflection-api/json 与 json-valueC API、ast-print-dump-ast未被维护。PROMPT 模板本身也复述了方法论的核心契约绝不假设输出——运行工具并读取它每个新.slang必须干净编译/运行slangc/slangi 退出码 0或得到干净的预期诊断CHECK 必须逐字来自真实输出本地没有 FileCheck 时SIMPLE filecheck测试会报告ignored由 CI 验证而DIAGNOSTIC_TEST与slangi INTERPRET本地即可验证因此适合的地方优先用后者。七、bundle 布局与元数据约定7.1 目录组织按被覆盖的源区组织例如coverage/emit/、coverage/legalize/、coverage/parser/每个 bundle 包含README.md固定包含四个小节## Intent意图## Functional coverage功能覆盖——每个 claim 单元格 测试钉死的内容 covers目标## Unreachable gaps不可达缺口## Doc gaps observed观测到的文档缺口——当一个可达行为本应被文档化时把它回馈给文档树若干.slang测试文件及少量_repro/复现文件findings 照常归档到_meta/findings/。7.2 manifest 中的角色声明与文档锚定的两棵树不同coverage bundle 在_meta/manifest.yaml中携带role: coverage并且不声明source_doc——只声明watched_paths它们特征化的编译器源码。源码变更而非文档变更是标记 coverage bundle 过期的唯一依据watched_paths_digest变化即视为 stale。7.3 一个真实 bundle 的解剖parser以coverage/parser/README.md为例可以完整看到上述约定如何落地front-matter标注generated: true、生成模型、generated_at、source_commit、watched_paths_digest并警告自动生成可能与源码漂移勿手改Intent说明目标是source/slang/slang-parser.cpp中欠练习的分支当时整体套件下行覆盖率 81.53%仍有 1363 行未触及并说明较老测试携带doc_ref指向pipeline/02-parse-ast.md而新测试已省略该字段——本树的权威是源码Functional coverage表格逐行列出 30 个测试每个测试钉死什么行为、对应哪个诊断码、covers指向哪里。例如operator-decl-invalid-symbol.slang不认识的运算符符号以 E20008 拒绝integer-literal-pointer-width-suffix.slangz/uz宽度后缀将0xFFz定型为intptr_t、0xFFuz定型为uintptr_t且-9223372036854775808z会从uintptr_t降级回intptr_tnested-generic-angle-bracket-disambiguation.slangArrayArrayint,2,3中尾随的被拆成两个泛型闭括号解释器下嵌套读取返回 9gpu-foreach-statement.slang__GPU_FOREACH(dev, dims, LAMBDA(uint3 id) { call; });形式解析并降低为kernelCall_0_wrapper(...)标记characterization-unverifiedUnreachable gaps表格用行号原因记录了为什么没写测试的完整论证例如Parser::ParseGLSLInterfaceBlockslang-parser.cpp:6463-6479被判定为死代码全source/中无任何调用者grep 只能找到声明与定义_fixIntegerLiteralslang-parser.cpp:8194-8252因守卫恒为假而不可达其唯一的 W40005 诊断没有任何输入能产生__magic_type修饰符触发 SIGSEGV作为崩溃记录为 finding 而非测试Doc gaps observed表格把应该写进文档但没写的行为回馈给文档树例如struct [attr] Name属性放置按版本门控2025 前静默接受、2025 弃用警告 E31204、2026 硬错误 E31205但pipeline/02-parse-ast.md的 modifier-parsing 一节并未提及Drift review记录生成后对源码行号漂移的复核过程例如 8 处行号引用被重新推导死代码声明被再次验证仍成立。八、实践要点小结对想要理解或扩展这套测试体系的读者以下是最关键的实践规则写作顺序先读METHODOLOGY.md本树契约与_common.mdMETA 块形状、指令、诊断测试规则、finding 格式再读目标源文件——只有在本树中读源码选测试是被允许的分类而非猜测每个输入跑出来的结果必须归入三类之一——干净预期输出钉真实 CHECK、干净的文档化诊断//DIAGNOSTIC_TEST钉精确的 E####、或崩溃/内部错误写 finding绝不写成通过测试无法确认正确性就打标签characterization-unverifiedtrue不是失败而是诚实的声明错误导数、错误布局这类看起来绿但可能错的测试正是该标签存在的理由不可达/死代码不要追记录到## Unreachable gaps可达但应文档化的行为记录到## Doc gaps observed两者都是交付物可复现优先重新运行whitebox-coverage.js即可重建整树manifest 的role: coverage与watched_paths保证只有源码变更才会让 bundle 过期。这套方法论的本质是用观察 分类 显式标注取代猜测 断言它让第三棵测试树在补足覆盖率的同时把不确定是否正确与已知是缺陷清晰地分离出来从而在不污染规范测试树的前提下把编译器中所有可到达、却从未被文档描述的分支纳入持续验证的视野。【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考