2026/10/16 1:53:17

CoreCLR 测试配置一次搞懂:从“测试没跑“到跑通

CoreCLR 测试配置一次搞懂:从“测试没跑“到跑通 CoreCLR 测试配置一次搞懂从测试没跑到跑通【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime新测试推上去CI 绿得可疑结果列表里却找不到它——这是给 dotnet/runtime 仓库贡献 CoreCLR 测试时最经典的困惑。问题通常不在测试代码而在工程文件里那几个 MSBuild 属性没配对构建系统靠它们决定一个测试工程是编译、执行、还是干脆隐身。这篇文章不照本宣科地罗列规范而是顺着测试为什么没跑这条排查线把 src/tests/Directory.Build.targets 里的判定逻辑拆开讲顺带覆盖新建工程、合并运行器和本地复现的完整路线。CI 里测试没跑先从工程声明查起 ⚠️如果你的测试没出现在执行结果里按顺序过一遍下面这张表绝大多数情况都能在这里对号入座现象 / 判断该设该检查哪个属性某个目标架构、平台上不该构建也不该运行CLRTestTargetUnsupported可带Condition任何配置下都要关掉这个测试DisableProjectBuild测试只服务于 crossgen2其他模式全要排除AlwaysUseCrossGen2跑 NativeAOT 会挂NativeAotIncompatibleGCStress 下必崩或必超时GCStressIncompatible依赖稳定 JIT 输出、反汇编检查类断言JitOptimizationSensitiveilasm/ildasm 往返、可回收程序集、R2R 分别不兼容IlasmRoundTripIncompatible/UnloadabilityIncompatible/R2RIncompatible这些开关最终都汇到一个布尔量_WillCLRTestProjectBuild上。它像一道门卫判定在 src/tests/Directory.Build.targets 里依次落定BuildAllProjects ! true时放行一旦置true则要求CLRTestPriority CLRTestPriorityToBuild才放行第 94-107 行随后CLRTestTargetUnsupported true且CLRTestBuildAllTargets ! allTargets、DisableProjectBuild true、NativeAotIncompatible true且TestBuildMode nativeaot各自一票否决。判定一旦为 false该文件会导入Common/nobuild.targets让全部构建目标直接空转——工程就合法地消失了。顺带两个容易漏的分支MonoAotIncompatible true只在 Mono 的 fullaot 变体minifullaot/llvmfullaot下取消构建AlwaysUseCrossGen2 true则直接排除RuntimeFlavor mono、nativeaot 模式和 sanitizer 构建这三种场景。还有个反直觉点优先级过滤是单向的。选跑 P2 时P0、P1 会自动带上反过来只跑 P0 时 P1、P2 不会触发。而且这个过滤只作用于批量构建BuildAllProjectstrue——平时本地全量构建时所有工程照常构建优先级只在批量 设定上限的场景里生效。四种 CLRTestKind你的测试算哪一种每个测试工程必须归属四种种类之一由工程里的CLRTestKind声明。没写的时候src/tests/Directory.Build.targets 第 19-20 行按OutputType推断CLRTestKind Condition$(CLRTestKind) and $(OutputType) ExeBuildAndRun/CLRTestKind CLRTestKind Condition$(CLRTestKind) SharedLibrary/CLRTestKind推断完同一个Choose块给四种类型各配一组内部开关是否编译源码、是否产出可执行文件、是否要跑、是否生成执行脚本!-- 绝大多数测试不写就是这种 -- CLRTestKindBuildAndRun/CLRTestKind !-- 同一 exe 换一组命令行参数再跑一遍 -- CLRTestKindRunOnly/CLRTestKind CLRTestProjectToRun..\Original\Original.csproj/CLRTestProjectToRun !-- 只做编译期校验或给别处当依赖 -- CLRTestKindBuildOnly/CLRTestKind !-- 公共托管测试库多个测试共享 -- CLRTestKindSharedLibrary/CLRTestKind四种类型的差别可以一句话概括BuildAndRun编译 执行 生成脚本标准形态也是默认。RunOnly自己不编译、不产出可执行文件但仍生成执行脚本。它靠CLRTestProjectToRun指向真正要跑的工程——构建系统会把这个引用转成ProjectReference先构建然后在FetchExternalProperties目标里用[MSBuild]::MakeRelative算出被引用工程的输出路径作为InputAssemblyName这套逻辑在 src/tests/Common/CLRTest.Execute.targets 的FetchExternalProperties目标里BuildAndRun则直接取$(AssemblyName).dll。脚本最终统一生成 BatchWindows与 BashUnix两份。BuildOnly编译产物但不执行、不生成脚本典型用途是编译期校验。SharedLibrary只编译成库供零个或多个测试引用不执行、无脚本。一个连带细节RunOnly工程生成的脚本会主动清掉RunningIlasmRoundTrip变量等于自带 ilasm 往返豁免——因为多个 RunOnly 可能并发调用同一个工程往返操作容易互相踩。优先级只用来降级不用来升级CLRTestPriority的默认值是 0src/tests/Directory.Build.targets 第 21 行兜底CLRTestPriority Condition$(CLRTestPriority) 0/CLRTestPriority。设计意图是P0 应同时是最重要和最小的测试集合所以新增测试默认就是 P0只有确实耗时或偏稳定的测试才显式降级。仓库里的对照很直观src/tests/GC/API/GC/Collect.csproj 写着CLRTestPriority1/CLRTestPriority同目录的Collect_Aggressive.csproj则显式标为 0——后者被归入必须每次都跑的核心集。压力模式排除六个开关 一个自动联动以下属性在工程文件里写true即可把测试从对应模式中剔除GCStressIncompatibletrue/GCStressIncompatible JitOptimizationSensitivetrue/JitOptimizationSensitive NativeAotIncompatibletrue/NativeAotIncompatible IlasmRoundTripIncompatibletrue/IlasmRoundTripIncompatible UnloadabilityIncompatibletrue/UnloadabilityIncompatible R2RIncompatibletrue/R2RIncompatible机制上GCStress 靠环境变量注入当本次运行带 GCStress 时执行脚本里会写出DOTNET_GCStress$(RunWithGcStress)见 src/tests/Common/CLRTest.Jit.targets 末尾的CLRTestEnvironmentVariable条目。一个容易忽略的自动联动只要某个源文件在工程里标了HasDisasmCheck即带反汇编模式检查构建系统会自动把该工程置为GCStressIncompatibletrue——GCStress 下生成的代码形态不可预期反汇编断言必然失效这个联动就写在 src/tests/Common/CLRTest.Jit.targets 的GetDisasmCheckData目标里。R2R / crossgen2 的三个属性别配成矛盾体围绕 ReadyToRun 测试一共三个相关属性!-- 本测试只验证 crossgen2所有模式下都按此编译 -- AlwaysUseCrossGen2true/AlwaysUseCrossGen2 !-- 处于 R2R 测试通过中但本测试不走标准 R2R 编译路径 -- CrossGenTestfalse/CrossGenTest !-- 本测试与 ReadyToRun 不兼容 -- R2RIncompatibletrue/R2RIncompatible对应到脚本生成实现都在 src/tests/Common/CLRTest.CrossGen.targetsR2RIncompatibletrue且运行模式要求 R2R脚本直接打印SKIPPING EXECUTION BECAUSE the test is incompatible with ReadyToRun并以 0 退出跳过执行。AlwaysUseCrossGen2true脚本里导出RunCrossGen21与CompositeBuildMode1走 crossgen2 的 composite 编译流程。生成的脚本会先写响应文件、调用$CORE_ROOT/crossgen2/crossgen2再跑 R2RDump 做--header --sc --val校验参数面包括--composite、--targetarch、--targetos、--method-layout:random、--hot-cold-splitting、--synthesize-random-mibc合成 PGO 数据等。前两个同时设置CrossGenTestfalse且AlwaysUseCrossGen2true构建直接报Setting both AlwaysUseCrossGen2 to true and CrossGenTest to false is non-sensical。另外使用 crossgen2 的测试会被自动置为IlasmRoundTripIncompatibletrue——AOT 产物没有可往返的 IL这条联动同样写在 src/tests/Common/CLRTest.CrossGen.targets 的头部。跨平台编译的三条铁律 CI 的流水线是先在一个地方把全部测试的托管部分编译一遍再分发到各平台只跑各自的子集。由此推出三条硬约束托管代码不得依赖目标平台做条件编译不得用条件包含Compile Include上的平台条件收放源文件不得用平台相关的DefineConstants/切换代码路径。换句话说你在 Windows 上编译过的测试代码在构建 farm 的那台机器上必须一个字不改地编译通过。两个衍生规则测试用到的任何System.Private.CoreLib类型和成员必须在所有平台都可供构建——编译器得能找到它们。某平台确实不支持时提供桩实现方法体直接throw new PlatformNotSupportedException()即可。NuGet 依赖不写进各个测试工程统一更新 src/tests/Common/test_dependencies/test_dependencies.csproj这个工程自身是BuildOnly集中承载包引用如Microsoft.Diagnostics.Tools.RuntimeClient、xunit.analyzers并把库运行时文件汇集到 Core_Root。还有两个习惯动作测试因活跃 bug 挂掉时给测试方法加[ActiveIssue]每个源文件开头带标准许可证横幅// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license.新建测试工程步骤与那个必踩的坑标准路线以src/tests/Exceptions/Finalization/Finalizer.csproj为模板把模板工程复制到src/tests下的新目录。删掉AssemblyName节点。这是最经典的坑——SDK 默认用目录名当程序集名模板里残留的显式声明会干扰构建系统对测试产物的定位与脚本生成评审时经常被点名。显式写下CLRTestKind与CLRTestPriority哪怕值等于默认值写出来更省事。加入源文件用 xUnit 的[Fact]标注测试方法。需要依赖时托管引用用ProjectReference Include../ManagedDll.csproj /CMake 原生工程用CMakeProjectReference Include../NativeDll/CMakeLists.txt /。后者会被ResolveCMakeNativeProjectReference目标识别CMakeLists转成NativeProjectReference构建完由CopyNativeProjectBinaries把原生二进制拷进测试输出目录。构建然后按重跑失败测试的路子本地验证一次。两条与入口点相关的约定退出码执行脚本期望成功退出码是 100src/tests/Common/CLRTest.Execute.targets 中CLRTestExitCode Condition $(CLRTestExitCode) 100/CLRTestExitCode。自己写Main时成功必须return 100失败返回任意非 100 值static public int Main(string[] notUsed) { try { // Test scenario here } catch (Exception e) { Console.WriteLine($Test Failure: {e}); return 101; } return 100; }多数情况不用手写Main对需要运行的 C# 测试工程合并运行器、独立构建、进程隔离等场景src/tests/Directory.Build.targets 第 537-543 行会自动注入src/tests/Common/XUnitWrapperGenerator源生成器作为 Analyzer由它找出所有[Fact]方法并生成Main逐个调用。把新 JIT 回归测试放进哪普通优化编译的回归测试不带调试信息直接丢一个src/tests/JIT/Regression_ro_2/Runtime_issue_number.cs进去就完事——该运行器递归包含目录下所有.cs不需要改工程文件也不为单个源文件建目录子目录可以用来分组。源文件通配source-glob目录里的源码会被直接编译进各自运行器。需要不同编译设置、独立程序集、原生依赖或进程隔离的测试在src/tests/JIT/Regression_2/Runtime_issue_number/下建独立工程源码与配套文件放一起别混进通配目录上层Regression_2.csproj以递归发现工程的方式接管它们不直接编译源码。Regression_3.csproj对src/tests/JIT/Regression_3/下的GitHub_*、DevDiv_*等基于工程的测试做同样的事。合并运行器与 stripe大测试组怎么切新建合并运行器的步骤参照src/tests/JIT/Methodical/Methodical_d1.csproj这类模板。它通过HasMergedInTeststrue/HasMergedInTests声明合并身份用MergedWrapperProjectReference加通配符如a*/**/*_d.??proj批量引入子工程再显式Compile包含共享源文件。如果这个组测试量大、GCStress 下跑不完设NumberOfStripesToUseInStress比如 10让压力测试能切分。合并运行器该值默认取 1按它生成 stripe 数份标记文件形如name.MergedTestAssembly.stripe.total.MergedTestAssemblyForStressCI 的压力任务据此把大组切成多条 stripe 并行执行。上限 99——写超了构建报Attempt to split into too many stripes. Either reduce stripe count, or add more entries to SequenceOfIntegers。本地手动复现时合并运行器接受这组标准参数[testFilterString] [-stripe whichStripe totalStripes]testFilterString目前支持dotnet test --filter那种简单形式测试完全限定名的子串。whichStripe是从 0 开始的条纹下标totalStripes是总数等价做法是设环境变量TEST_HARNESS_STRIPE_TO_EXECUTE其值必须形如.whichStripe.totalStripes。收尾排查清单与下一步读哪段源码测试隐身时按序过一遍工程是不是RunOnly而CLRTestProjectToRun没指或指错CLRTestKind是否被设成了BuildOnly/SharedLibrary本就该不执行上表的兼容性属性是否全部true与当前 CI 的模式GCStress / NativeAOT / R2R正好撞上CLRTestTargetUnsupported的Condition是否命中了当前TargetArchitecture/RuntimeFlavor批量构建时优先级是否高于本次的CLRTestPriorityToBuild退出码是否按 100 约定返回自定义Main返回 0 表示失败脚本照样判绿。想继续深挖按这条线读src/tests/Directory.Build.targetsKind 推断、_WillCLRTestProjectBuild判定、stripe 标记文件、NativeAOT 配置与源生成器注入→src/tests/Common/CLRTest.Execute.targetsBatch/Bash 脚本生成与InputAssemblyName→src/tests/Common/CLRTest.CrossGen.targetsR2R 脚本与报错分支→src/tests/Common/CLRTest.Jit.targetsilasm 往返、反汇编检查、GCStress 注入。每遇到一个测试为什么被跳过的疑问都能在这四份文件里找到对应的判定分支。【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考