详解:21 天全平台零失败判定标准与修复流程)
Aspire 测试解除隔离政策Unquarantine Policy详解21 天全平台零失败判定标准与修复流程【免费下载链接】aspireAspire is the tool for code-first, extensible, observable dev and deploy.项目地址: https://gitcode.com/GitHub_Trending/as/aspire本文基于 Aspire 仓库的 docs/unquarantine-policy.md 官方政策文档系统讲解被隔离Quarantined的 flaky 测试在何种条件下可以解除隔离、修复者应该做什么与不该做什么并结合仓库中的隔离测试属性实现、隔离 CI 工作流、复现工作流与 QuarantineTools 源码给出可直接落地的实操流程与底层原理。读完本文你将掌握 Aspire 仓库中修复偶发失败测试 → 验证修复 → 等待观测窗口 → 解除隔离的完整闭环以及其中 21 天观察期背后的统计依据。一、从隔离到解除隔离先理解测试隔离机制在进入解除隔离政策之前需要先建立对隔离测试的完整认知。Aspire 仓库将当前已知不稳定flaky但尚未修复的测试标记为隔离测试使其从常规 CI 中排除避免产生误报false negatives这一机制的详细说明见 docs/quarantined-tests.md。隔离的载体是[QuarantinedTest]特性其源码实现位于 tests/Aspire.TestUtilities/QuarantinedTestAttribute.cs。该特性实现了 xUnit 的ITraitAttribute在编译期把quarantinedtrue这一 trait 附加到测试方法或类、程序集上随后 CI 脚本与命令行过滤器就可以据此包含或排除这些测试[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] public sealed class QuarantinedTestAttribute : Attribute, ITraitAttribute { public string? Reason { get; } public QuarantinedTestAttribute(string? reason null) { Reason reason; } public IReadOnlyCollectionKeyValuePairstring, string GetTraits() [new KeyValuePairstring, string(quarantined, true)]; }Reason参数通常用于存放追踪 issue 的链接或说明文字。例如 docs/quarantined-tests.md 中给出的用法[Fact] [QuarantinedTest(https://github.com/microsoft/aspire/issues/7920)] public async Task FlakyTest() { // Test implementation }常规 CI 通过--filter-not-trait quarantinedtrue排除隔离测试而隔离 CIquarantine CI则用--filter-trait quarantinedtrue反向只跑隔离测试用于持续观测其稳定性。隔离测试的完整生命周期为标记为隔离 → 在隔离 CI 中持续运行观测 → 修复底层问题 → 满足解除隔离条件后移除特性。而何时可以移除[QuarantinedTest]特性正是 docs/unquarantine-policy.md 这份解除隔离政策文档要回答的核心问题。二、核心政策21 天 × 全平台零失败解除隔离政策的正文只有一条硬性标准docs/unquarantine-policy.md一个被隔离的测试只有在隔离 CI 运行中于所有操作系统Windows、Linux、macOS上连续至少 21 天保持零失败才允许被解除隔离。这里有两个关键限定词所有操作系统不是修复者本地通过、也不是某一个平台通过而是 Windows、Linux、macOS 三个平台全部通过。因为不少 flaky 问题具有平台特异性——例如路径分隔符、文件系统行为、Docker/容器运行时差异、密钥链keychain行为等只在特定平台暴露。连续 21 天强调的是持续稳定性sustained reliability而非单次或短时间的观察结果。一次偶然的绿色不能证明修复是持久的。政策文档同时强调了一个容易被误解的前提仅仅提交代码修复是不足以解除隔离的A code fix alone is not sufficient。修复只是必要条件而不是充分条件充分条件是在观测窗口内被数据证明稳定。观测数据从哪来政策文档明确指出每个测试对应的 GitHub issue从 meta-issue 8813 关联而来中的失败跟踪数据会按操作系统分别记录通过/失败率。也就是说解除隔离的决策依据是每个平台的通过/失败率数据而非代码审查或主观判断。关于观测频率的补充说明政策文档原文表述隔离 CI 工作流tests-quarantine.yml每 6 小时运行一次。从当前仓库的实际工作流配置 .github/workflows/tests-quarantine.yml 看其调度 cron 表达式为0 */2 * * *即每 2 小时一次并在注释中说明频繁运行隔离测试是为了捕获低失败率的 flaky 测试。无论按文档的 6 小时还是当前配置的 2 小时计算观测数据点的数量级是一致的且更高的运行频率只会让数据更充分。三、为什么是 21 天低频偶发失败的统计依据21 天不是拍脑袋的数字政策文档给出了明确的计算依据部分 flaky 测试的失败率很低1%5%或者只在特定条件下失败负载、时段、runner 硬件差异。这类低频偶发失败的特点是单次或几次运行很难撞见失败因此修复是否真正生效也无法用少量样本判断。按每 6 小时运行一次 × 21 天计算每个操作系统可积累约84 个数据点三个平台合计约252 个数据点。即便某个测试的潜在失败率低至 1%5%在 252 个样本中如果修复有效真实失败率归零出现连续全绿的概率极高反过来如果修复无效残留的偶发失败几乎必然会在如此多的样本中暴露出来。这就是 21 天窗口能够提供高置信度high confidence的原因——它把修复是否持久这一判断建立在统计学上有意义的样本量之上。四、修复隔离测试的正确姿势DO 与 DO NOT政策文档对修复者给出了非常具体的操作纪律共四条前两条是应该做后两条是绝对不要做类别操作说明✅ DO应用修复并推送apply your fix and push it正常提交代码修复✅ DO通过复现工作流验证修复reproduce-flaky-tests.yml在提交前用专门工作流压测验证❌ DO NOT在同一个 PR 里解除隔离必须保留[QuarantinedTest]特性❌ DO NOT关闭跟踪 issue21 天窗口的监控由独立流程负责达标后由其关闭 issue修复后的真实走向政策文档描述了解除隔离的完整时序修复者推送修复代码测试继续留在隔离 CI 中运行如果修复正确失败率会逐步下降到 0%连续 21 天零失败后由独立流程或人工执行解除隔离操作并关闭跟踪 issue。这里的关键设计是职责分离修复者只负责修代码和验证而判定是否达标由独立的监控流程根据跟踪数据完成。这正是第一节政策文档中决策由审查跟踪数据的一方做出而不是由提交修复的 agent 做出的体现——防止自己修复、自己判定、自己解除带来的利益冲突与误判风险。五、21 天窗口背后的 CI 机制隔离测试如何持续运行与观测解除隔离政策依赖的观测数据来自隔离 CI 的持续运行。这一机制的实现细节在 .github/workflows/tests-quarantine.yml 中包含三种触发方式定时调度schedulecron 每 2 小时运行一次用于频繁捕获低失败率的 flaky 测试手动触发workflow_dispatch可按需手动执行PR 触发pull_request路径过滤仅限tests-quarantine.yml、specialized-test-runner.yml、run-tests.yml、build-cli-e2e-image.yml等编排类工作流文件本身——即只有改动 CI 基础设施时才触发一次隔离测试作为 sanity check避免每次 CI/eng 改动都触发全量隔离/外环测试工作流注释中明确记录了此前因路径过滤过宽导致磁盘空间问题的历史。隔离测试的执行管线quarantine_tests作业复用了专门的specialized-test-runner.yml工作流关键参数为testRunnerName: QuarantinedTestRunsheetBuilder使用隔离测试专用的 runsheet 生成器attributeName: QuarantinedTest按该特性筛选测试extraRunSheetBuilderArgs: -p:RunQuarantinedTeststrue开启隔离测试运行模式extraTestArgs: --filter-trait quarantinedtrue只运行带quarantinedtruetrait 的测试ignoreTestFailures: true隔离测试的失败不导致流水线变红。最后一项是理解21 天观测的关键隔离测试本来就是为了容忍失败而存在的所以其失败会被ignoreTestFailures吞掉不会阻塞 PR 合并失败数据通过测试结果文件TRX被记录和跟踪用于后续的稳定性评估。Runsheet 生成器的 MSBuild 实现位于 eng/QuarantinedTestRunsheetBuilder/QuarantinedTestRunsheetBuilder.targets它导入共享基类 eng/SpecializedTestRunsheetBuilderBase.targets并配置PropertyGroup SpecializedTestTypequarantined/SpecializedTestType SpecializedTestProperty/p:RunQuarantinedTeststrue/SpecializedTestProperty SpecializedTestRunsheetBuilderNameQuarantinedTestRunsheetBuilder/SpecializedTestRunsheetBuilderName /PropertyGroup共享基类负责先以--list-tests列出匹配的测试用退出码 8 表示无匹配测试再为 Windowsbuild.ps1与 Linux/macOSbuild.sh分别生成 JSON runsheet供 GitHub Actions 在三个平台矩阵上执行。整个流程还会跳过 Playground 类测试项目。基础设施失败与测试失败的分流工作流中还有一个值得注意的设计report_infra_failure作业在隔离测试作业失败时触发仅限定时调度且仓库为上游时。由于隔离测试失败被ignoreTestFailures吞掉一个失败的隔离运行只能意味着基础设施坏了此时会去创建/追加一个去重后的 automation-broken issue而测试本身的失败不会生成 issue——失败数据只作为观测记录存在。这保证了 21 天零失败统计的纯净性观测到的每一次失败都是测试自身的失败而不是 CI 基础设施故障的噪声。六、修复后的第一道验证reproduce-flaky-tests 工作流政策文档明确要求修复者在推送后用reproduce-flaky-tests.yml验证修复。该工作流的完整配置见 .github/workflows/reproduce-flaky-tests.yml其设计目标是在多个 OS runner 上反复运行指定测试以压测方式确认修复的有效性。使用步骤官方注释修改文件顶部CONFIGURATION段的 env 变量填入要复现的测试推送到自己的分支手动触发gh workflow run reproduce-flaky-tests.yml --repo repo --ref your-branch在多个 OS 与 runner 上检查结果。可配置参数环境变量作用示例/取值范围TEST_PROJECT测试项目短名映射到tests/Aspire.{name}.Tests/或tests/{name}.Tests/Hosting、Core、Dashboard、Components.Common、Cli.EndToEndTEST_FILTER传给dotnet test的过滤参数放在--之后--filter-method *.TestMethodName、--filter-class *.TestClassName可多个叠加TARGET_OSES目标操作系统逗号分隔ubuntu-latest,windows-latest,macos-latestRUNNERS_PER_OS每个 OS 的并行 runner 数正整数总作业数 RUNNERS_PER_OS × OS 数≤ 256ITERATIONS_PER_RUNNER每个 runner 执行的迭代次数正整数工作流的setup作业会先校验配置合法性RUNNERS_PER_OS与ITERATIONS_PER_RUNNER必须是正整数、TARGET_OSES只能包含三个已知 runner、TEST_FILTER不能仍是占位符YourTestMethodName否则直接报错退出避免浪费 runner 资源。迭代执行与零测试检测reproduce作业按OS × runner 序号生成矩阵每个 runner 独立循环执行指定迭代次数。每次迭代之间会清理testresults目录并杀掉残留的dcp进程避免上次运行的状态污染本次结果。每次迭代使用dotnet test --project 项目路径 --no-restore --no-build -- \ --timeout 10m \ --results-directory .../testresults \ TEST_FILTER值得注意的是零测试检测逻辑由于仓库在Testing.props中配置了--ignore-exit-code 8即使一个测试都没匹配上进程退出码也可能是 0。因此工作流会解析输出日志若退出码为 0 但日志中没有total: [1-9]之类的计数就判定为零测试执行并以退出码 8 处理——避免假通过掩盖过滤配置错误。整个运行结束后会输出汇总表项目、过滤条件、OS、runner 数、迭代数任一 runner 有失败则整体失败。七、真正移除隔离标记QuarantineTools 命令行工具当 21 天零失败的观测期结束、由独立流程判定达标后解除隔离的具体操作——从源码中移除[QuarantinedTest]特性——可由仓库自带的命令行工具完成。其实现位于 tools/QuarantineTools/Quarantine.cs基于 RoslynMicrosoft.CodeAnalysis对测试源码做结构化的安全编辑而不是粗暴的文本替换。命令格式# 隔离一个测试必须提供 issue URL quarantine -q Namespace.Type.Method -i issue-url # 解除隔离一个或多个测试 quarantine -u Namespace.Type.Method核心参数参数说明-q/--quarantine隔离模式为指定测试添加[QuarantinedTest(issue-url)]-u/--unquarantine解除隔离模式移除指定测试上的[QuarantinedTest]特性-i/--url隔离时必填的 issue URL必须是合法的 http/https 地址-m/--mode模式quarantine默认或activeissue操作Xunit.ActiveIssueAttribute-a/--attribute自定义要增删的特性全名默认按 mode 推断-r/--root要扫描的 tests 根目录默认repo/tests工具的工作原理从 tools/QuarantineTools/Quarantine.cs 源码看其执行流程为定位仓库根向上查找.git目录默认扫描tests下的全部.cs文件跳过bin、obj、.git、artifacts、node_modules等目录预过滤先用正则快速判断文件是否包含目标方法名或特性名避免对所有文件做昂贵的 Roslyn 解析解析语法树对候选文件构建 C# 语法树定位方法声明并计算其所在的命名空间与嵌套类型链与用户传入的Namespace.Type.Method支持嵌套类型语法精确匹配增删特性隔离时若方法上尚无该特性则追加[QuarantinedTest(issue-url)]并确保文件包含using Aspire.TestUtilities;解除隔离时移除特性且仅在文件中不再有任何QuarantinedTest特性残留时才移除 using 指令幂等与安全若目标已处于期望状态则不做任何修改编辑时保留文件原有换行风格CRLF/LF只写回发生变化的文件并打印更新清单。这种按语法树编辑的方式避免了文本替换可能破坏代码格式、注释或错误命中同名标识符的问题是解除隔离操作值得推荐的安全路径。对应的单元测试见 tests/QuarantineTools.Tests/QuarantineScriptTests.cs。八、日常排查与过滤命令速查无论是验证修复还是调试隔离测试都需要掌握基于 trait 的过滤命令。以下命令整理自 docs/quarantined-tests.md排除隔离测试运行常规 CI 的做法dotnet test --filter-not-trait quarantinedtrue使用直接测试运行器dotnet exec YourTestAssembly.dll --filter-not-trait quarantinedtrue只运行隔离测试排查/调试用dotnet test --filter-trait quarantinedtruedotnet exec YourTestAssembly.dll --filter-trait quarantinedtrue对于在自动化环境如 Copilot agent中运行测试的场景docs/quarantined-tests.md 特别强调始终使用隔离过滤以避免误报dotnet test --filter-not-trait quarantinedtrue是正确的打开方式而裸的dotnet test会把隔离测试一并跑进来产生无意义的失败信号。九、例外情形何时可以缩短观察期政策文档在严格标准之外给出了两条例外均需 maintainer 酌情判断at maintainer discretion基础设施缺陷导致的隔离如果测试是因为测试基础设施的 bug而非时序/竞态问题被隔离且修复明显正确则可以采用更短的观察期。理由很直观基础设施缺陷的修复通常是一次性的、确定性的不需要用大量样本去排除低频偶发因素。超高失败率骤降为零如果隔离前失败率极高80%且修复后立即降到 0%maintainer 可以选择提前解除隔离。80% 的失败率意味着问题几乎必然复现修复后立即归零本身就具有很高的证据价值。但无论哪种例外政策都强调一条不可动摇的原则解除隔离的最终决策由审查跟踪数据的一方做出而不是由提交修复的 agent 决定In all cases, the decision to unquarantine is made by reviewing the tracking data, not by the agent that applied the fix。这是整个政策体系中防止既当运动员又当裁判的关键防线。十、总结解除隔离的完整决策链综合 docs/unquarantine-policy.md 与仓库实现Aspire 的解除隔离政策可以概括为一条完整、可审计的决策链隔离不稳定测试通过[QuarantinedTest(reason)]实现于 tests/Aspire.TestUtilities/QuarantinedTestAttribute.cs打上quarantinedtruetrait退出常规 CI持续观测隔离 CI.github/workflows/tests-quarantine.yml按 cron 定时在 Windows、Linux、macOS 三平台运行全部隔离测试失败不阻塞合并、只记录数据基础设施故障与测试失败严格分流修复与验证修复者提交修复后用 .github/workflows/reproduce-flaky-tests.yml 在多个 OS/runner 上反复压测验证但不移除隔离标记、不关闭跟踪 issue数据达标连续 21 天、三平台零失败约 252 个数据点后由独立流程/维护者根据 per-OS 通过率数据判定达标解除隔离使用 tools/QuarantineTools/Quarantine.cs 提供的-u模式结构化移除[QuarantinedTest]特性并清理 using 指令测试回归常规 CI 队列。这套政策的核心价值在于把修复 flaky 测试这种容易陷入主观判断的工程活动转化为由统计样本量、跨平台观测与职责分离共同保证的客观流程——对维护者、对自动化的 Copilot 类 agent、对 CI 系统本身都给出了清晰且可执行的规则边界。相关配套文档可进一步阅读 docs/quarantined-tests.md隔离机制与过滤命令以及 docs/ci/specialized-test-failure-issues.md专项测试失败 issue 的分流策略。【免费下载链接】aspireAspire is the tool for code-first, extensible, observable dev and deploy.项目地址: https://gitcode.com/GitHub_Trending/as/aspire创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考