Files
AtomUI/docs/engineering/workflows/affected-verification.md

18 KiB
Raw Permalink Blame History

按改动影响选择验证

本文定义 AtomUI 日常验证入口、影响选择规则和结果有效性。Bug 修复采用“精确复现 → 模块迭代 → 测试去留与清理 → 最终模块验证”; 不能把 dotnet test AtomUI.slnx 当作默认或完整测试清单。测试项目由仓库实际 .csproj 发现,不依赖解决方案是否收录。

测试是否长期保留及 Bug 修复完成条件由测试价值与生命周期规范定义。 临时复现测试验证完成后默认删除,最终模块验证认证清理后的工作树;缺少有效证据、测试处置或清理不能宣称修复完成。 全量仅在用户明确要求或发版时运行,不是每次修复的默认收尾步骤。

日常入口

在仓库根目录使用 Python 3.9+;工具仅使用标准库。测试与构建沿用仓库 .NET SDK 和现有 VSTest/xUnit。

# 查看本地暂存、未暂存、新增、删除和重命名输入对应的计划
python3 scripts/verification/test.py plan

# Bug 修复开发过程:必须显式限制到缺陷模块,可重复传入 --path
python3 scripts/verification/test.py run --scope iterate \
  --path src/AtomUI.Desktop.Controls/Popup \
  --path tests/AtomUI.Desktop.Controls.Tests/Popup

# 全量回归:仅在用户明确要求或发版时运行;Bug 修复与普通改动只验证所修改的模块
scripts/run-full-regression.sh

# 分支/CI affected 比较:必须显式提供比较基线
python3 scripts/verification/test.py run --scope change --base origin/main

# 需要额外验证时只追加,不替换自动选择的测试
python3 scripts/verification/test.py run --scope iterate \
  --path src/AtomUI.Desktop.Controls/ScrollViewer \
  --include-test 'tests/AtomUI.Desktop.Controls.Tests/ScrollViewer/**/*.cs'

origin/main 是基线参数示例,使用实际目标分支。--base 使用与 HEAD 的 merge-base;默认 HEAD 只比较工作区, 工作区干净时报告 no-changes,不会声称已验证整个分支。所有运行认证当前工作树;暂存版本与工作树不同的文件 单独列出,不能用这份报告声称暂存快照已经验证。

plan --path <path> 可以反复传入路径试算,输出明确标记为 preview。run --scope iterate --path <path> 是实际的 Bug 修复模块验证,只认证这些显式路径;它不读取工作树中的其他改动来扩大范围,也不能作为 change/full 证据。 --json 输出全部测试类、源文件、命中理由、编译项目、覆盖缺口和专项义务。

CLI 硬拒绝以下误用:iterate 未传 --path、focused --path 搭配 change/full、本地无 --base 的 change、以及 agent 通过 test.py run --scope full 发起全量。--tests-only 保留给 CI test phase;agent 不得把它作为本地绕过参数。 iterate 只使用模块自身的所有权、符号和测试文件关系,不自动扩大到跨模块显式规则和消费者传播;这些关系用于 显式分支/CI change 计划或已获授权的全量。适用专项义务按独立流程验证,不因 focused 通过而免除,也不自动触发全量。

明确请求或发版执行的全量若出现失败,当前全量进程继续执行以一次收集完整失败清单。每个失败分别回到精确测试和 focused 模块验证; 所有失败局部转绿后,只再运行一次最终全量确认。不得在每个失败修复后分别启动全量,也不得因全量失败而丢弃已经收集的其他项目结果。

选择依据

策略位于 scripts/verification/test-policy.json;实现位于 scripts/verification/affected/。

  1. 所有权:维护生产源码根与测试源文件模式,按控件家族或基础设施子域分组。基础控件与 Desktop 同一家族 可以共享所有权,测试项目不必与生产项目同名。
  2. 消费关系:C# 类型、扩展方法与 AXAML 资源声明提供正向影响线索。项目依赖方向限制运行时消费关系, 向外传播以实际改动文件的新旧声明为起点,保留直接控件家族的测试;消费者仅由实际引用文件的声明选择, 不连带同目录中无关类型。直接消费者进入验证范围,继承关系继续传播。组合控件的整个公开类型不会无限传播,否则一个 Button 容易把所有 窗口及窗口内所有控件重新带入全量。
  3. 源码读取测试:测试代码引用的输入文件名建立独立索引,不受 ProjectReference 限制。它只增加选择, 不用于排除测试;同名文件可能保守地多选。文件读取关系优先于普通文档忽略规则。
  4. 显式共享规则:主题扫描、Popup 清单、Catalog/XLIFF、Gallery 结构、共享生成源码、图片加载、资源生命周期、 没有项目引用的本地化集成测试等,使用明确的附加规则。目录扫描和动态依赖不能仅靠符号匹配推断。
  5. 保守退回:直接改动没有精确测试归属时运行完整所有者测试组并报告原因。没有对应测试的间接消费者纳入编译, 在报告中明确标记为只有编译证据。无法找到所有者、规则引用不存在的 suite 时,报告 gap 并拒绝运行成功。

符号分析是保守的静态辅助,不是完整 C#/XAML 语义分析器,也不保证发现任意反射、配置拼接、资源 key 计算或 外部程序的依赖。新增这类路径必须增加显式策略,评审时解释其消费边界。发布/显式全量基线发现选测遗漏后,应先把 遗漏关系加入策略和工具回归测试,再修复产品问题,避免相同路径再次漏选。

测试类从真实 C# 声明发现,支持 partial、嵌套类、块命名空间和组合 Fact/Theory 属性,不从文件名猜类名。 测试 helper、全局初始化、AssemblyInfo、snapshot、测试资源等发生变化时,扩大到所属测试项目。

测试与构建成本

runner 用临时 solution 汇总选中项目,一次构建共享依赖;构建保留各项目声明的 TFM,不能把 netstandard 生成器 强制构建成 net10。随后测试使用 net10 与 --no-build --no-restore,并在 runsettings 中保存过滤器,避免命令行 长度限制。Restore 交给增量构建判断,不通过永久 --no-restore 隐藏新依赖。

构建与测试串行持有当前 checkout 的 OS 文件锁。保持原有 UI 程序集的串行测试约束,不通过关闭隔离约束提速。 该锁只协调统一入口;外部直接 dotnet build 不遵守锁,所以执行器同时检查 build 后与测试结束时的产物指纹。

无新改动时可以复用成功 receipt,但必须同时匹配:

  • HEAD、暂存差异、所有 Git 管理/未忽略输入的当前内容;删除和新文件也参与。
  • 完整测试计划、配置、SDK 信息、Python、平台和环境变量指纹。
  • 选中构建图与测试运行依赖的产物内容,包括生成器与其他 TFM;产物被删除、替换或重建后重新验证。 集成测试新建的独立 fixture 产物不属于被锁定的测试依赖,允许正常生成。

环境变量只保存摘要,不写入报告。--fresh 禁用 receipt 复用;CI 始终使用 fresh。 规划过程中、规划后、验证过程中输入变化都不能认证成功;缓存复用时也重新检查输入。

结果契约

报告位于忽略的 .artifacts/verification/latest.json;receipt.json 保存最近一份可复用成功证据, history.json 最多保留 100 条耗时记录,CLI 的总耗时包含仓库发现、审计和影响规划。metrics 命令显示历史。日志、临时 solution、runsettings、TRX 全部放在 系统临时目录,正常退出、测试失败和可处理的中断后清理;强制杀进程后遵循系统临时目录清理策略。

状态 含义 退出码
passed 本次计划内检查完成,不代表整个仓库已验证 0
reused 相同输入/配置/产物匹配已有通过证据,保留原验证时间 0
iteration-passed 指定模块验证通过,不代表 change/full;Bug 修复还需满足测试价值规范的完成门禁 0
tests-passed 显式 --tests-only 的测试阶段通过,专项义务仍单独列出 0
no-changes 当前比较范围没有改动,没有执行测试 0
pending 测试通过,但仍有专项验证义务 2
blocked 影响规则有缺口或指向不存在的 suite 2
failed 构建、测试、证据或快照一致性失败 1

测试进程退出 0 不足以成功:必须有 TRX,选中测试数大于零,每个选中类有实际通过的结果,计数与逐项结果一致, 不存在失败、跳过或未执行结果。默认不把跳过的测试折算为通过。新增平台 skip 必须明确调整验证范围或提供对应 平台证据,不能通过忽略返回码解决。

普通文档可以得到不运行 .NET 测试的计划;被测试或生成器消费的文档先按输入关系选择验证。 每次收尾仍执行 git diff --check,并保留原始 UX/手动验证要求。

专项义务

发布注册、生成器、共享编译源码、产品项目配置、native 能力、打包资产等会产生 obligations。这些义务不因为 单元测试通过而消失,runner 返回 pending;单元测试证据可以复用,但不代表专项已完成。执行报告指定的真实 publish、package 或平台 验证,按 AOT 编程规范、 Gallery 发布流程 和受影响平台文档汇报独立证据。

check --id <计划中的义务 ID> -- <验证命令及参数> 只执行与策略中该义务的 verifier 数组完全相同的命令, 记录当前输入/环境、完整命令、退出状态、耗时和输出摘要。多步骤义务必须先实现并审查覆盖全部步骤的验证脚本, 再将其完整 argv 注册到策略;不能用 quick 命令、单独 publish、启动进程或说明文字冒充完整验证。 当前发布/平台义务跨越多个现有手动流程,策略没有为它们预注册未经验证的统一 verifier;默认保留 pending, 按现有专项流程提供独立证据。工具拒绝未注册命令,不提供临时绕过。注册 verifier 的策略修改本身也需评审和验证。 随后再次执行同一 run,已通过的单测可以复用,当前输入下通过的专项证据附在报告中;失败或输入改变后的旧证据 不能满足义务。CI 需要专项检查时,同样在 run 前调用对应 check,使用已经验证的发布/平台脚本。

工具不提供 --ignore-obligations 或仅凭文字声明生成成功 receipt 的开关。NativeAOT publish 也不等于 UI 行为验证; hover、滚动、裁剪、资源释放等仍保留原始复现条件和交互/生命周期检查。

依赖与 SDK 变更需要目标平台/TFM 验证;全量回归仍只在用户明确要求或发版时运行,focused 执行不会为了扩大范围自动运行全量。

全量、CI 和维护

# 仅在用户明确要求或版本发布时使用
scripts/run-full-regression.sh

# 维护影响规则时的快速检查
python3 scripts/verification/test.py audit
python3 scripts/verification/test.py self-test
python3 scripts/verification/test.py metrics

.github/workflows/verify-affected.yml 在 PR 上按基线选择测试,保存短期紧凑报告;手动 workflow 的 full test phase 是 CI 例外,仍使用 test.py --tests-only 生成机器可读证据。agent 本地最终全量只使用带进度、ETA、继续收集失败的脚本。 全量脚本失败时通过 [DIAGNOSTICS] 输出系统临时目录中的项目日志路径,保留完整异常、断言与清理阶段的栈; 不得只根据测试名推断失败原因。成功项目日志自动清理,失败日志在完成排障后清理,不写入仓库测试产物目录。 CI 没有定期全量任务,不改仓库 branch protection;是否要求该 job 通过由仓库管理员配置。 该 job 明确命名为 Selected tests and impact report,使用 --tests-only,成功只表示选中的测试阶段通过; 专项义务仍保留在 JSON 中,必须由发布/平台检查单独覆盖。这个 job 不承担发布可用性 gate,不能把它的绿色结果 当成 NativeAOT、package 或平台行为已经验证。普通本地 run 不带此参数,专项未满足时仍返回 pending/退出码 2。

新增测试项目必须有生产路径映射;新增生产根必须有所有者或显式规则。audit 校验全仓库映射与测试项目发现, 不会因为当前 PR 没有触碰遗漏路径就放过新缺口。修改选择器时运行真实临时 Git 仓库和进程边界测试;测试必须 证明漏选、旧产物、空结果或错误输入会被识别,不能只断言配置文件包含某行文字。

收尾报告说明实际范围、命令、通过数量、耗时、复用情况、只有编译证据的消费者和未完成专项;Bug 修复同时提供 测试价值规范要求的测试处置与清理记录。CLI 成功仅是执行证据,不自动完成价值审查和修复验收。 若精确选择频繁退回整个 owner,先完善对应功能域与共享规则,再考虑拆分测试项目。按测试价值规范删除低价值或 临时测试,但不得靠削弱仍有效的契约或原始复现条件降低耗时;清理同步维护映射,不能忽略 gap 或零测试。

Conditional registration backends

src/AtomUI.Toolchain/Backends/ILLink8、src/AtomUI.Toolchain/Backends/ILC8、 src/AtomUI.Toolchain/Common/Registration 与 scripts/registration 有独立的策略所有权。 这四个区域的 tests: [] 表示目前没有可以直接证明其编译器闭包语义的常驻单测; 它不是忽略项,也不把其他后端的测试冒充为这些实现的覆盖。计划保留 conditional-registration-engines、native-aot 和 package-layout 专项义务; 即使编译成功或没有可选单测,也不能把结果称为后端验收通过。

已有测试继续属于其真实实现:AtomUI.TypeMap.Linker.Tests 验证官方 net10 后端的 元数据、物化和回执契约;AtomUI.Build.Tasks.Tests 验证工具身份、任务进程、链接输入 和发布回执;生成器测试验证生成的公共注册契约。修改这些模块时,现有区域映射仍选择 对应测试。Toolchain 的 Common 由相应 profile 编译复用,需验证真正消费被改文件的 Tasks、ILLink8、ILLink10 或 ILC8 构建入口。统一项目的默认 Tasks build 不等于验证其他 profile;只有 Common 的零测试计划不提供 全部工具 profile 的编译或运行证据。

src/AtomUI.Toolchain 根区域维护单一项目和 profile 接线,继续选择已有的任务宿主、生成器注入边界与 TypeMap 后端测试。Common/Localization 和 Common/SourceGeneration 分别选择其已有语言模型、代码命名与 资源 wrapper 测试;最长路径归属使这些规则不会吞并 ILLink8/ILC8 的按需专项边界。 策略中的 toolchain-consolidated-source-deletions 仅为工程合并保留旧路径删除的影响选择:Git 仍跟踪的 旧文件删除会触发原有 Tasks/TypeMap 测试。它不表示旧目录或项目仍存在,也不创建兼容源码副本。

专项按实际改动选择,不能用 .NET 10 单测或手工标记来注销 .NET 8 引擎义务:

影响范围 必须执行的专项与现有入口
ILLink8 hook、Common binder、Core8 发布视图 固定 ILLink 8.0.27 的真实 SDK trim;覆盖实例化、反射/type checks、泛型晚到达、static/signature-only 否定、传递闭包/循环/组请求、选中 thunk 的 builder 参数,以及实际执行和输出 PE;输入/模板变坏必须在 Mark 前失败。构建入口及冷消费要求见 Toolchain 构建与验证和注册契约。
ILC8 adapter、driver、upstream 或 maintainer bootstrap 按 src/AtomUI.Toolchain/Backends/ILC8/README.md 构建固定源码/工具,并执行 verification/verify_backend.py --compiler-output <owned-output>;scanner 与 no-scanner、优化、原生表/GC/预初始化分别验证。该机制脚本只证明其实际执行的组合,正式字符串 ABI、SDK 输入、资源和冷包仍须独立消费。
net10 混合 ABI bridge、SDK 输入/输出阶段 用预编译 net8-only 包验证所改 managed/NativeAOT/Browser 路径,记录原件→转换副本→实际引擎输入→正式输出→部署字节;纯 net10 不依赖桥工具且不改写;伪造 accessor、缺协议、未知模板和工具冲突须拒绝。Browser AOT 的 IL stripping 是新的编译输出阶段,不能与转换前 DLL 哈希混为一谈。
工具打包或消费 targets 正常构建的 nupkg、全新包/HTTP cache、一次 restore、随后 publish --no-restore;不手工补 cache/包文件,不在消费者里构建 runtime 或工具。核对应用运行依赖不含工具,验证实际最终输出、平台启动与资源行为。

以上义务包含依赖主机和平台的多阶段检查,当前没有一个固定、无参数命令能完整验证全部 组合,因此没有在策略中把某个部分脚本登记为完整 verifier。runner 会保留 pending; 交付记录须逐项说明实际命令、输入版本、运行结果、未验证平台及临时测试清理。 不得注入旧日志或伪造 specialist receipt 使 pending 消失。临时消费者和探针仍按测试生命周期 规范在验证后删除;必要的短日志与版本摘要不进入默认测试集合。