Slang 诊断目录测试包:为每个诊断代码生成 DIAGNOSTIC_TEST 的系统化方案
2026/9/19 10:53:47 网站建设 项目流程

Slang 诊断目录测试包:为每个诊断代码生成 DIAGNOSTIC_TEST 的系统化方案

【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang

导读

Slang 编译器将用户可见的诊断(错误、警告、提示)组织成一份集中式的诊断目录:source/slang/slang-diagnostics.luasource/compiler-core/下的*-diagnostic-defs.h头文件共同定义了全部 695 个诊断代码。本篇文章围绕docs/generated/tests/design/cross-cutting/diagnostics-catalog/_prompt.md这份生成契约,完整讲解 Slang 测试框架中"每个诊断代码对应一个最小复现测试"的诊断目录测试包(diagnostics-catalog bundle):从测试的元数据(//META)契约、catalog-digest摘要校验、DIAGNOSTIC_TEST:SIMPLE指令、文件命名规范,到硬性规则、README.md结构、质量检查清单与禁止事项,并深入仓库源码揭示诊断目录的生成管线与运行时渲染机制。读完本文,你将掌握如何为任意一个 Slang 诊断代码编写最小复现测试、如何通过regenerate.py工具链校验测试与目录快照的一致性,以及如何理解诊断严重级别、警告分组与错误渲染格式的底层实现。

一、什么是诊断目录测试包

diagnostics-catalog是 Slang 测试框架中一个非常特殊的测试包(bundle)。普通测试包以叙事文档为"源真相",从文档中的技术声明(claim)推导测试;而本包是 agentic-tests 计划 §15.3 要求的系统性负测试扫描(systematic negative-test sweep)每个 Slang 编译器能够发射的诊断代码,对应一个DIAGNOSTIC_TEST文件,用最小复现输入触发该诊断,并断言诊断流中出现目标代码(如E30019W41024)。

其特殊之处在于:本包的"源真相"不是叙事文档,而是编译器源码中的结构化 LUA / 宏式诊断目录。实际产物位于 docs/generated/tests/design/cross-cutting/diagnostics-catalog/,目录中包含数百个形如30019-type-mismatch.slang15302-include-recursion.slang38000-no-entry-point-found.slang的测试文件,以及记录整体覆盖情况的README.md与生成契约_prompt.md

二、为什么这个测试包在 doc-anchoring 契约下成立

框架的通用规则(见 docs/generated/tests/_meta/prompts/_common.md)是"测试必须锚定到文档声明,而非源码"。诊断目录测试包被视为一个例外,原因在于编译器源码中的诊断目录本身是结构化规范来源(structured spec source)

  • source/slang/slang-diagnostics.lua 与source/compiler-core/下的*-diagnostic-defs.h(如 slang-lexer-diagnostic-defs.h、slang-core-diagnostics.h)中,每一条目都是(code, severity, name, message)的声明式元组;
  • 这组元组定义了编译器向用户提供的诊断契约——"这个编译器会发射哪些诊断"的权威枚举;
  • 它们不是任意实现源码,因此目录文件本身即可充当规范。

于是,本包中每个测试的doc_ref锚定到目录条目本身(LUA 中的err()调用行或DIAGNOSTIC()宏行),而不是叙事文档中的某个段落;同时叙事文档 docs/generated/design/cross-cutting/diagnostics.md 仍被引用用于总体框架(框架、严重级别模型、共享 ID 命名空间)。

需要强调:目录条目是"编译器能够发射"的诊断,不是"某个输入必然触发"的承诺。条目的名称只能提示触发构造,无法保证能够从最小输入到达该代码——这正是本包要系统性验证并记录的事实(见下文"Codes dropped")。

三、输入资料:目录快照、分桶文件与叙事文档

生成测试时可供使用的输入包括四类:

输入路径作用
完整目录快照docs/generated/tests/_meta/diagnostics-catalog/catalog.txt695 个代码的权威快照,含 severity / name / source / message 列
分桶文件docs/generated/tests/_meta/diagnostics-catalog/uncovered-bucket-<N>.txt(N=0..4)分配给本生成桶的未覆盖代码子集
叙事文档docs/generated/design/cross-cutting/diagnostics.md框架与既有锚点参考
编译器源码source/slang/slang-diagnostics.lua仅用于验证代码能从最小复现输入到达,禁止挖掘目录未规定的行为

3.1 目录快照的结构

快照文件以注释头说明统计信息:总代码 695,测试代理已覆盖 82,未覆盖 613。其列格式为code<TAB>covered<TAB>severity<TAB>name<TAB>source<TAB>message,例如:

1 UNCOVERED err cannot-open-file slang-diagnostics.lua cannot open file '~path' 10000 UNCOVERED error illegalCharacterHex slang-lexer-diagnostic-defs.h illegal character (0x$0)

观察可知:代码按来源分属两类目录——slang-diagnostics.lua中的 LUA 条目(代码区间如 1–105、15000–15616、20000+ 等)与*-diagnostic-defs.h中的 C 宏条目(如词法器诊断 10000–10012),且旧式宏使用$0占位符、LUA 条目使用~name占位符,渲染时统一为E<5位代码>前缀。

3.2 分桶机制

操作者会并行派发五个代理,每个代理负责一个uncovered-bucket-<N>.txt。例如uncovered-bucket-0.txt的头部说明其包含 123 个代码(范围 1..20102),列格式与快照一致。硬性要求是:只处理自己桶内的代码,不得染指其他桶

四、每个测试的契约://META 元数据块

每个.slang测试文件必须以//META块开头,逐字段填写(契约在 docs/generated/tests/_meta/prompts/_common.md 通用规则基础上扩展):

//META: generated=true //META: model=<your model id> //META: generated_at=<ISO 8601 UTC> //META: source_commit=<HEAD> //META: doc_ref=source/slang/slang-diagnostics.lua ← 或相应的 -defs.h //META: doc_section_digest=<output of `_meta/regenerate.py catalog-digest <code>`> //META: purpose=Fires diagnostic E<code> (<name>) — <message> //META: intent=negative //META: pipeline_stage=<lex | parse | check | lower | ir-pass | emit | link> //META: catalog_code=<code> //META: catalog_name=<name from catalog> //META: warning=Auto-generated. May drift from source. Do not edit by hand.

字段要点:

  • doc_ref:指向声明该代码的目录文件——source/slang/slang-diagnostics.lua或对应的*-diagnostic-defs.h。这是"目录条目即规范"契约的直接体现。
  • doc_section_digest:必须运行_meta/regenerate.py catalog-digest <code>获取,不要自己哈希(见第五节)。
  • pipeline_stage:声明触发该诊断的流水线阶段,可选值为lexparsechecklowerir-passemitlink。词法器/预处理器诊断落在lex/parse,语义检查诊断落在check,发射期诊断落在emit等。
  • catalog_codecatalog_namecatalog_codelint重新计算摘要的键,必须与目录中的拼写完全一致;同时它使 grep 变得微不足道:
grep "catalog_code=30019" docs/generated/tests # 找到代码 30019 的测试

4.1 实际测试文件示例

仓库中已落地的 30019-type-mismatch.slang 完整展示了上述契约:

//META: generated=true //META: model=claude-opus-5[1m] //META: generated_at=2026-08-04T00:00:00+00:00 //META: source_commit=7e725f15572c6589ee6d738a8856fb3348f11617 //META: doc_ref=source/slang/slang-diagnostics.lua //META: doc_section_digest=72ffe778cb00717e39fbbd393cb340c63dd3002cfddf50b2a64064e805d41409 //META: purpose=Fires diagnostic E30019 (type-mismatch) - type mismatch in expression //META: intent=negative //META: pipeline_stage=check //META: catalog_code=30019 //META: catalog_name=type-mismatch //META: warning=Auto-generated. May drift from source. Do not edit by hand. // 只有初始化器可转换到声明类型时初始化才成功。三分量向量不能转换为 // 标量 int,CHECK 钉住类型不匹配错误。 //DIAGNOSTIC_TEST:SIMPLE(diag=CHECK,non-exhaustive): void main() { int x = float3(1,2,3); } //CHECK: E30019

注意该文件在//META块后附有 2–4 行说明注释,解释"验证什么声明、如何验证"(测试的推理,而非引用文档原文)——这是通用规则 docs/generated/tests/_meta/prompts/_common.md 的强制要求。

五、catalog-digest:摘要计算与漂移检测

doc_section_digest是目录条目按code<TAB>severity<TAB>name<TAB>message四列拼接后的sha256,数据来自_meta/diagnostics-catalog/catalog.txt。必须运行 docs/generated/tests/_meta/regenerate.py 的子命令catalog-digest生成,而不是手工哈希——因为lint以完全相同的方式重算摘要,任何列或分隔符的手工偏差都会被报告为漂移(drift)。

python3 docs/generated/tests/_meta/regenerate.py catalog-digest 30019

配套机制:

  • regenerate.py lint会重算每一条目摘要,对已漂移的条目发出警告;
  • 这意味着目录条目被改名或消息被修改时,恰好只有该代码对应的测试失效,实现精准的失效传播;
  • 测试包README.md明确说明:注释标记必须写作//CHECK:(斜杠后无空格),因为 runner 只识别这种形式;此前曾因// CHECK:带空格被当作普通注释解析,导致测试"通过"却没有断言任何内容——这是本包修复过的一个真实历史问题(见 README.md)。

六、测试体:最小复现与 DIAGNOSTIC_TEST 指令

测试体是触发诊断的最小输入

//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK,non-exhaustive): // <minimum reproduction here> // CHECK: E<code>

关键规则:

  • non-exhaustive的取舍:当编译器对同一输入发射多于一个诊断时使用non-exhaustive(这很常见——语义检查常伴随候选 note 等次级诊断),只匹配目标代码即可;如果所有发射的诊断都已标注,则不要添加non-exhaustive——runner 会拒绝多余的non-exhaustive;反之若漏标,穷举模式会失败。
  • 精确代码:CHECK 必须写精确代码(E30019W41024等),slang-test 按代码匹配,不能只写error:
  • CHECK 变体:同一文件中可写多条// CHECK:(或用CHECK-DAG处理乱序匹配);诊断消息通常同时发射短消息 + 长消息 + 次级 note,无法确定措辞时应断言稳定的错误代码。
  • 指令变体DIAGNOSTIC_TEST也可携带目标与入口参数,如实际目录中的36111-unexpected-capability.slang使用//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK,non-exhaustive):-target hlsl -stage compute -entry main -profile sm_6_039022-vk-index-without-vk-location.slang使用-target spirv -stage fragment -entry main

七、文件命名约定

每个测试文件按<code>-<kebab-case-name>.slang命名,使整个目录可按代码与名称双向浏览:

代码文件名示例
3001930019-argument-type-mismatch.slang
1530215302-include-recursion.slang
4102441024-name-shadow-warning.slang
3800038000-no-entry-point-found.slang

仓库中实际落地的文件严格遵循此约定,例如10000-illegal-character-hex.slang15000-end-of-file-in-preprocessor-conditional.slang41016-using-uninitialized-variable.slang36111-unexpected-capability.slang等。

八、硬性规则

生成测试时必须遵守以下硬性规则:

  1. 限定桶内:不写桶文件之外的代码测试,其他代理并行处理其他桶。
  2. 不合成无法触发诊断的代码:尝试 2 次后仍找不到能触发某代码的最小输入,放弃该代码;在README.md## Codes dropped (could not reach from minimum input)中列出,附一行原因,例如:
    • "internal diagnostic with no user trigger"(内部诊断,无用户触发途径)
    • "requires multi-file test"(需要多文件测试)
    • "requires API surface not available to slangc CLI"(需要 slangc CLI 不可用的 API 表面)
  3. 不锁定 bug 行为:如果编译器实际发射的内容与目录消息不符,放弃该代码,不要编写断言错误输出的测试。
  4. 一码一测:一个测试文件只对应一个代码;多代码挤在一个文件会破坏目录的 grep 能力。
  5. 全部严重级别均在范围内:error、warning、note、info 都要覆盖;对它们统一使用intent=negative(我们测试的是"编译器发射了正确的诊断"这一行为,而非功能)。

九、README.md 结构

第一个进入目录的代理创建README.md,同波次后续代理追加其桶的行。规范结构如下(实际仓库的 README.md 可作范例):

  1. Front-matter(YAML,遵循通用规则):
--- generated: true model: <your model identifier> generated_at: <ISO 8601 timestamp, UTC> source_commit: <git HEAD when you ran> watched_paths_digest: <sha256 from regenerate.py digest> source_doc: docs/generated/design/cross-cutting/diagnostics.md source_doc_digest: <sha256 of source_doc file at source_commit> warning: "Auto-generated. May drift from source. Do not edit by hand." ---

其中watched_paths_digestsource_doc_digestpython3 docs/generated/tests/_meta/regenerate.py digest <bundle>计算。 2.## Intent:一段话说明这是目录扫描包、每个测试是什么、以及这是 agentic-tests 计划 §15.3 的交付物。 3.## Catalog coverage:覆盖统计表:

| Bucket | Codes in bucket | Tests added | Codes dropped |
  1. ## Functional coverage:每行一个测试(每个诊断代码即一个 claim),按代码排序。列遵循通用 Coverage 规则:Claim | Intent | Anchor | Tests。本包中Claim单元格即测试的//META: purpose行(通常为Fires diagnostic E<code> (<name>) — <message>);IntentnegativeAnchorsource/slang/slang-diagnostics.lua或相应的*-diagnostic-defs.hTests为单个<code>-<name>.slang链接。
  2. ## Codes dropped (could not reach from minimum input):每个被放弃代码一行,附放弃原因。

实际仓库README.md还额外维护了一套声明簿记(framing claims F1–F7 与逐条目 claim 家族 E1),并给出关键统计:快照共 695 个条目、331 个已有测试、67 个有 dropped 行、297 个既无测试也无 dropped 行(即覆盖缺口)、8 个测试引用了快照不含的代码——这展示了该枚举机制如何让"无复现的条目"从不可见变为可见。

十、质量检查清单

每个测试提交前必须逐项确认:

  • build/Release/bin/slang-test下报告100% of tests passed (1/1)
  • CHECK 模式钉住精确诊断代码(E30019,而不是仅仅error:);
  • 仅当编译器发射目标诊断之外的诊断时使用non-exhaustive
  • 文件名为<code>-<kebab-name>.slang
  • catalog_code与文件中的代码一致;
  • 测试体是最小的——没有多余函数、没有无关构造;
  • 无法从单个.slang文件触发的代码被放弃(不用 hack 合成)。

十一、禁止事项

  • 不要mark-fresh(由操作者处理);
  • 不要 commit;
  • 不要修改其他代理的测试(各桶独立);
  • 不要写桶外代码的测试;
  • 不要包含编译器实现中的源码行内容(只允许目录条目本身)。

十二、仓库源码纵深:诊断目录从 LUA 到 C++ 的生成管线

理解测试包锚定的"目录条目"之前,有必要看清目录本身在仓库中如何产生。docs/generated/design/cross-cutting/diagnostics.md 给出了完整管线,这正是本测试包doc_ref所指对象的来源:

  1. source/slang/slang-diagnostics.lua(约 6400 行)通过调用errwarningstandalone_noteinternalfatal等辅助函数声明编译器的全部诊断;
  2. source/slang/slang-diagnostics-helpers.lua 收集这些调用、校验它们,并把每条消息解析为带类型的参数与 span;文件末尾调用process_diagnostics返回处理后的列表,校验失败则抛出 Lua 错误;
  3. slang-rich-diagnostics.h.lua加载处理后的列表,提供模板所需的映射辅助函数(toPascalCasegetCppTypegetSeverityEnumgetWarningLevelEnum);
  4. 内嵌在slang-rich-diagnostics.h/slang-rich-diagnostics.cpp中的 FIDDLE 模板为每条目生成 C++:namespace Slang::Diagnostics中的结构体(每个参数与位置一个成员)、插值消息并构建 span 的toGenericDiagnostic()方法,以及携带代码/严重级别/名称/警告组的DiagnosticInfo常量。

声明辅助函数的签名(以err(name, code, message, primary_span, ...)为例)固定了参数顺序。消息中的~name:Type是带类型插值参数,validate_diagnostic会拒绝非 kebab-case 名称及error/warning/note/internal/fatal之外的严重级别。运行时侧,source/compiler-core/slang-diagnostic-sink.cpp 中的DiagnosticSink负责渲染:getEffectiveMessageSeverity依次应用源级 warning 状态跟踪、按 ID 的严重级别覆盖、警告组门控与TreatWarningsAsErrors提升;渲染头格式为<severity>[E<5-digit id>]: <message>——因此warning 也以E前缀渲染(如warning[E41016]),测试断言时统一使用E<code>

12.1 严重级别与警告组

source/compiler-core/slang-diagnostic-sink.h 声明了严重级别枚举Severity { Disable, Note, Warning, Error, Fatal, Internal }(与公共头 include/slang.h 的SLANG_SEVERITY_*通过static_assert对齐),以及警告组枚举WarningLevel { Default, All, Extra, Pedantic }。两组是独立而非嵌套的:sink 的启用位掩码默认只打开Extra位,因此Extra级警告开箱即发,AllPedantic级警告需显式启用;对应 CLI 开关是-Wall/-Wextra/-Wpedantic。此外存在 9 个fatal级条目(如 E39901cannot-process-include、E40002cyclic-reference、E55206generic-specialization-recursion-cycle、E56003use-of-uninitialized-opaque-handle),fatal errorinternal error渲染后会通过SLANG_ABORT_COMPILATION中止编译;而internal条目(internal-compiler-errorunimplementedunexpected,均为代码 99999)报告的是编译器自身缺陷,输入到达它们属于编译器 bug,不应作为受支持的复现——这正是本测试包不为 99999 写测试的原因。

12.2 目录消息中的占位符与别名

LUA 目录条目支持~name:Type类型化插值参数与~decl.name成员访问(生成器据此生成decl->getName()等 C++ 代码),还支持variadic_span/variadic_note(每个元素渲染为独立记录)。另外,目录中存在一个诊断别名:overlappingBindingsparameterBindingsOverlap;别名仅在用户通过名称引用诊断时可见(-warnings-disable-warnings-as-errors-W<id>-Wno-<id>经由findDiagnosticByName解析操作数),因此旧的-warnings-disable overlappingBindings用法继续有效。

十三、总结:这份契约解决了什么问题

诊断目录测试包把"诊断契约"从编译器内部的结构化数据变成了可执行、可 grep、可漂移检测的回归测试资产:

  • 系统性:以目录快照(695 个代码)为真值表,逐条目枚举,未覆盖代码在## Untested claims/## Codes dropped中显式记账,覆盖缺口不再隐形;
  • 可维护catalog-digest+lint把目录条目改名/改消息精确映射到对应测试失效,漂移立即可见;
  • 可检索<code>-<name>.slang命名 +catalog_code元数据让grep "catalog_code=30019"一步定位测试;
  • 诚实性:只写"最小输入确实触发的诊断",无法到达的代码记录原因而非强行合成,同时拒绝锁定编译器 bug 行为。

对于需要深入 Slang 编译器诊断系统的读者,建议按以下顺序阅读仓库:先看本包 README.md 的声明簿记与统计,再对照 catalog.txt 快照与任意一个 30019-type-mismatch.slang 测试,最后回到 source/slang/slang-diagnostics.lua 与 docs/generated/design/cross-cutting/diagnostics.md 理解条目声明与运行时渲染的完整闭环。

【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询