
.NET Runtime 测试进程隔离RequiresProcessIsolation属性使用完全指南【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtimeRequiresProcessIsolation是 dotnet/runtime 仓库 CoreCLR 测试基础设施中的一个关键 MSBuild 属性。默认情况下src/tests下的测试项目会被合并merge进共享的测试运行器merged test runner进程以节省资源但某些测试因修改进程级状态、依赖独立的输出目录布局或需要自定义入口点而无法共享进程。本文以仓库文档 requiresprocessisolation.md 为主体结合 src/tests/Directory.Build.targets 与 XUnitWrapperGenerator 等源码系统梳理设置该属性的全部 30 条规则、底层实现原理与实操建议。什么是 RequiresProcessIsolation为什么需要它在 dotnet/runtime 仓库中src/tests目录下的大量 CoreCLR 测试如 JIT、GC、Loader、Interop 测试默认会被合并到一个共享的测试运行器进程中执行。这样做可以显著减少进程启动开销、降低测试流水线的资源占用。但共享进程意味着环境变量、AppContext开关、运行时宿主配置等进程级状态在多个测试之间共享测试的输出文件被汇总到统一的运行器目录目录布局不再由单个测试项目决定测试的入口点由运行器统一生成自定义Main无法生效一次崩溃或Environment.Exit会杀死整个共享进程导致其余测试全部失败。RequiresProcessIsolation正是为了解决这些问题而存在。当属性被设置为true时测试项目不再参与合并而是被构建为独立的可执行文件并在自己的进程中运行。从源码可以印证这一点src/tests/Directory.Build.targets 中当$(RequiresProcessIsolation) true and $(CLRTestKind) ! SharedLibrary时MSBuild 会把OutputType强制推断为ExeOutputType Condition$(SkipInferOutputType) ! true and (($(IsMergedTestRunnerAssembly) true and $(BuildAllTestsAsStandalone) ! true) or ($(RequiresProcessIsolation) true and $(CLRTestKind) ! SharedLibrary))Exe/OutputType也就是说RequiresProcessIsolation与BuildAsStandalone、IsMergedTestRunnerAssembly是三种并列的独立成体机制它们决定了测试究竟以共享运行器、独立可执行文件还是运行器本体merged runner assembly的形式产出。与合并测试运行器的协同在构建合并测试运行器IsMergedTestRunnerAssembly true时MSBuild 会显式探查哪些被引用的项目需要进程隔离。DiscoverProcessIsolatedTestProjectReferences 目标会对所有ProjectReference调用GetRequiresProcessIsolation目标src/tests/Directory.Build.targets#L507-L513该目标会返回设置了RequiresProcessIsolation的项目及其IsRunnable状态将这类项目从普通的ProjectReference中移除避免被合并以OutputItemTypeOutOfProcessTests、ReferenceOutputAssemblyfalse的方式重新引用使其作为独立的进程外测试与运行器一起交付。同时XUnitWrapperGenerator 源码生成器会读取 MSBuild 传来的build_property.RequiresProcessIsolation与build_metadata.AdditionalFiles.IsOutOfProcessTestAssembly见 OptionsHelper.cs为进程隔离测试生成OutOfProcessTest测试项XUnitWrapperGenerator.cs#L53-L59并在运行器生成的主入口中写入进程外测试执行计划OutOfProcessPlanFile见 XUnitWrapperGenerator.cs#L389-L412——宿主据此逐项以独立进程启动这些测试。注意在 Browser 目标上进程外测试需要宿主侧编排无法自行在浏览器中启动新进程。构建时还会在测试输出目录写入$(AssemblyName).OutOfProcessTest标记文件src/tests/Directory.Build.targets#L465-L471供下游工具识别该程序集必须进程隔离执行。设置规则全景何时必须开启进程隔离仓库文档给出了 30 条明确的触发规则分为项目文件触发与源码触发两大类。只要命中任意一条就必须在测试项目的.csproj/.ilproj中设置PropertyGroup RequiresProcessIsolationtrue/RequiresProcessIsolation /PropertyGroup下面按触发维度分组说明全部规则。一、进程级状态依赖规则 1–2规则 1项目设置了CLRTestEnvironmentVariable或CLRTestBatchEnvironmentVariable这两类 item 会为测试进程设置环境变量。环境变量是进程级状态如果测试被合并进共享进程其他需要不同取值的测试将无法共存。例如 GC/JIT 相关测试经常通过环境变量注入 stress 配置这类测试天然无法共享进程。规则 2项目设置了RuntimeHostConfigurationOption或其简写属性展开后生成该 item运行时宿主配置选项在进程启动时一次性读取作用于整个进程。RuntimeHostConfigurationOption对应的就是System.Runtime.Hosting与运行时特性开关如System.GC.Server、System.Threading.ThreadPool.MinThreads等。合并进程中的任何测试都无法在启动后再改变这些配置。特别地AutoreleasePoolSupport以及定义在 src/tools/illink/src/ILLink.Tasks/build/Microsoft.NET.ILLink.targets 中的一系列 feature-switch 简写属性都会展开为RuntimeHostConfigurationOptionitem。从该文件中可以看到RuntimeHostConfigurationOptionitem 的Trim true特性会被收集为裁剪器trimmer的 feature 设置见 Microsoft.NET.ILLink.targets#L251即它同时影响运行时开关与链接期行为。因此任何使用这些简写属性的测试都必须进程隔离。二、构建与链接模式不兼容规则 3–5、16、21、25、28、29规则 3项目设置了TrimMode裁剪trimming设置会改变测试的编译与链接方式。被裁剪的测试无法合并进未裁剪的运行器——两者对引用程序集的裁剪处理不一致会导致行为差异或缺失引用。规则 4项目包含CMakeProjectReference测试依赖由 CMake 工程构建的原生 C/C 库。原生二进制必须出现在测试自身的输出目录中才能被加载而合并到共享运行器目录后无法保证这一点。从构建逻辑看ResolveCMakeNativeProjectReference与CopyNativeProjectBinaries目标src/tests/Directory.Build.targets#L223-L239会把这些原生产物复制到项目OutDir这正是进程隔离测试所需要的可预期目录布局。规则 5项目把文件复制到输出目录通过Content或None配合CopyToOutputDirectory复制的文件如测试数据、配置文件、自定义清单在合并场景下可能与其他测试的文件冲突或因目录被改写而找不到。典型场景是.il汇编输入、预期结果文件等。规则 16项目设置了CrossGenTestfalse/CrossGenTest该测试与 CrossGen2 预编译ahead-of-time compilation不兼容。合并运行器走统一流程而此类测试需要自己的执行脚本跳过 crossgen 步骤。若需要设置此属性测试必须进程隔离以便生成独立的执行脚本。规则 21测试需要显式Main入口点某些测试需要自定义Main例如解析命令行参数以支持本地调试使用async Main测试运行时崩溃 / 未处理异常场景此时进程退出码exit code是关键断言在任何测试代码运行前修改程序集可见性或进行 interop 初始化。合并测试运行器的入口点是自动生成的自定义Main与其冲突。规则 25项目使用了自定义AppManifest嵌入自定义应用程序清单例如为 COM 激活准备的 manifest的测试必须在独立进程中运行因为合并运行器不会包含每个测试各自的 manifest。规则 28测试使用IlcMultiModule或多模块 NativeAOT 构建多模块 NativeAOT 测试具有不同的构建与链接行为与标准合并运行器流水线不兼容。规则 29测试依赖特定的框架编译设置如果测试依赖框架本身以非默认设置编译例如UseSystemResourceKeys则必须运行在与这些设置匹配的运行时进程中共享运行器的运行时未必满足要求。三、逐进程跳过检查规则 6–10、14这一类规则的共同点是跳过判断发生在进程内部只有独立进程才能被单独跳过。合并到共享进程后跳过逻辑要么无法独立执行要么会误伤其他测试。规则 6GCStressIncompatibletrue/GCStressIncompatible执行脚本会检查DOTNET_GCStress在启用 GC stress 时跳过该测试。从 CLRTest.Execute.Bash.targets 与 CLRTest.Execute.Batch.targets 可以看到该属性会生成环境兼容性检查脚本片段。值得注意的是CLRTest.Jit.targets#L116 中HasDisasmCheck true时也会自动把GCStressIncompatible置为true——即带 JIT 反汇编检查disasm 检查的测试会自动进入进程隔离。规则 7JitOptimizationSensitivetrue/JitOptimizationSensitive测试对 JIT 优化级别敏感在 JIT stress 模式、min-opts 或分层编译激活时被跳过跳过检查按进程执行。规则 8SuperPMICollectIncompatibletrue/SuperPMICollectIncompatibleSuperPMI 采集运行期间跳过该测试。规则 9IlasmRoundTripIncompatibletrue/IlasmRoundTripIncompatibleIL 往返校验IL round-trip validation运行期间跳过该测试。规则 10UnloadabilityIncompatibletrue/UnloadabilityIncompatible测试无法在可卸载的AssemblyLoadContext中运行当设置了RunInUnloadableContext时按进程跳过。规则 14IsLongRunningGCTesttrue/IsLongRunningGCTest长时运行的 GC 测试仅在设置RunningLongGCTests时执行。跳过逻辑通过 CLRTest.GC.targets 以 shell 前置命令pre-command注入按进程执行因此需要独立进程。四、构建/执行过滤规则 11–13规则 11CLRTestTargetUnsupportedtrue/CLRTestTargetUnsupported测试在当前目标平台上不受支持影响构建期过滤与测试执行。典型场景是仅支持 Windows 或仅支持特定架构的测试在交叉目标如非 Windows 平台上被过滤掉。规则 12NativeAotIncompatibletrue/NativeAotIncompatible测试无法在 NativeAOT 模式下编译或运行影响构建与执行过滤。src/tests/Directory.Build.targets#L14-L16 展示了类似的过滤模式如 Mono AOT 变体下禁用构建。规则 13MonoAotIncompatibletrue/MonoAotIncompatible测试与 Mono 的 AOT 编译器不兼容影响 Mono AOT 测试运行的构建与执行过滤。构建时还会生成$(AssemblyName).NoMonoAot标记文件见 src/tests/Directory.Build.targets#L445-L447。五、进程行为与退出语义规则 17–20、23、26、27规则 17源码调用Environment.Exit()Environment.Exit会终止整个进程。若测试被合并调用它会把共享运行器一起杀死导致后续测试全部无法执行。任何使用Environment.Exit断言进程终止行为的测试都必须独立成体。规则 18源码调用GC.WaitForPendingFinalizers()这是进程级 GC 操作。在共享进程中执行会干扰其他测试的 GC 状态造成非确定性失败。GC API 测试目录如 src/tests/GC/API 下的 Collect、Finalize、GetTotalMemory 等项目大量涉及此类进程级 GC 语义因此这些项目普遍设置了RequiresProcessIsolation。规则 19源码使用可回收的AssemblyLoadContext创建、加载并卸载 collectibleAssemblyLoadContext会修改进程级程序集状态。共享进程中不同测试的加载/卸载行为会相互冲突。规则 20源码修改进程级状态包括设置AppContext开关或数据对哪些程序集已加载 / 未加载做出假设监控 JIT 编译方法数量这类测试依赖全局计数器测试方法返回后仍遗留运行中的次要线程。从规则 20 可以看出判断原则的本质只要测试的行为会污染进程使后续测试无法回到干净状态就必须隔离。规则 23测试是另一个测试启动的独立可执行文件如果项目产出的是被其他测试项目通过Process.Start启动的辅助可执行程序helper exe它必须构建为独立可执行文件并进程隔离。规则 26源码设置了模块级特性如[module: SkipLocalsInit]模块级特性影响整个程序集的所有代码。合并到共享运行器后其他期望默认行为的测试会受到影响例如SkipLocalsInit会改变局部变量初始化语义从而改变未初始化内存的行为。规则 27测试注册了进程级事件处理器且不清理例如在AssemblyLoadContext.Default.ResolvingUnmanagedDll上注册处理器而返回前不注销会泄漏给共享运行器中的后续测试干扰其原生库解析行为。六、崩溃与错误注入类测试规则 22、24规则 22测试故意让进程崩溃验证崩溃行为的测试栈溢出、致命错误处理、未处理异常必须在自己的进程中运行否则崩溃会杀死共享运行器中的其他测试。这类测试通常还配合非默认的CLRTestExitCode使用见规则 30。规则 24测试从特定路径加载原生库相对于输出目录探测原生库、或验证自定义原生库加载行为的测试需要可预期的目录布局合并运行器无法保证。七、退出码语义规则 30规则 30项目设置了非默认的CLRTestExitCodeCLRTestExitCode由测试包装脚本在进程层检查。期望特定非默认退出码的测试例如期望崩溃退出码COR_E_EXECUTIONENGINE不得为任何跳过条件使用 XUnit 级别的跳过特性如[SkipOnCoreClr]因为 XUnit 级别跳过的返回码是 100会与期望的自定义退出码冲突。此类测试的所有跳过逻辑必须使用进程级机制如GCStressIncompatible、JitOptimizationSensitive。为什么从 XUnitWrapperGenerator 生成的代码可以印证进程外测试的执行计划流程在写完计划后执行return 100;XUnitWrapperGenerator.cs#L389-L404。XUnit 跳过返回 100 是基础设施约定与崩溃类测试断言的退出码体系相悖因此这类测试必须进程隔离且只用进程级跳过机制。速查表一项目文件级触发条件如果项目文件中包含以下任一 MSBuild 属性或 item请设置RequiresProcessIsolationtrue/RequiresProcessIsolationProperty/Item原因CLRTestEnvironmentVariable进程级环境变量CLRTestBatchEnvironmentVariable进程级环境变量RuntimeHostConfigurationOption或其简写属性进程级宿主配置TrimMode不兼容的构建模式CMakeProjectReference依赖原生二进制Content/None且CopyToOutputDirectory输出目录冲突GCStressIncompatible逐进程跳过检查JitOptimizationSensitive逐进程跳过检查SuperPMICollectIncompatible逐进程跳过检查IlasmRoundTripIncompatible逐进程跳过检查UnloadabilityIncompatible逐进程跳过检查IsLongRunningGCTest逐进程跳过前置命令CLRTestExecutionArguments非空逐进程调用参数CLRTestTargetUnsupported构建/执行过滤NativeAotIncompatible构建/执行过滤MonoAotIncompatible构建/执行过滤CrossGenTest设为false需要跳过 crossgenAppManifest逐进程清单IlcMultiModule不兼容的构建模式CLRTestExitCode非默认值自定义退出码XUnit 级跳过返回 100与期望码冲突速查表二源码级触发条件如果源码.cs或.il包含以下任一模式请设置RequiresProcessIsolationtrue/RequiresProcessIsolation模式原因Environment.Exit(终止进程GC.WaitForPendingFinalizers(进程级 GC 副作用可回收AssemblyLoadContext进程级程序集状态显式 / 自定义Main入口点与合并运行器入口点不兼容async Main与合并运行器入口点不兼容故意使进程崩溃会杀死共享运行器修改AppContext状态进程级状态对已加载程序集做出假设进程级状态遗留运行中的次要线程进程级状态从相对路径加载原生库依赖目录布局供其他测试启动的独立辅助 exe必须是独立可执行文件[module: SkipLocalsInit]等模块级特性影响整个程序集未注销的进程级事件处理器泄漏给后续测试需要非默认框架编译设置需要匹配的运行时设置一个真实的判断流程与验证方法当你在src/tests下编写或修改一个 CoreCLR 测试时可以按以下顺序自查项目文件是否命中速查表一中的任一属性/item重点检查环境变量、宿主配置简写属性、TrimMode、CLRTestExitCode、CrossGenTest等源码是否命中速查表二中的任一模式重点检查Environment.Exit、GC.WaitForPendingFinalizers、自定义Main、可回收 ALC、模块级特性若都不命中则可以安全地留在合并运行器内若命中任意一条加上RequiresProcessIsolation属性。验证方式构建产物层面设置该属性后项目的OutputType会被推断为Exesrc/tests/Directory.Build.targets#L17输出目录会出现$(AssemblyName).OutOfProcessTest标记文件src/tests/Directory.Build.targets#L465-L471运行器层面合并运行器构建时会通过GetRequiresProcessIsolation目标发现该测试并将其从合并集合移到OutOfProcessTestssrc/tests/Directory.Build.targets#L515-L529若某测试位于合并测试目录如 JIT 目录下由若干分组项目覆盖的测试却把OutputType设为Exe且未设置RequiresProcessIsolation/BuildAsStandalone/IsMergedTestRunnerAssembly构建会直接报错——这正是CheckForTestOutsideOfGroup目标src/tests/Directory.Build.targets#L133-L141在BeforeTargetsBuild时执行的守护检查其错误信息明确要求使用这三者之一。与相邻机制的关系BuildAsStandalone同样使测试独立成体但不依赖本文所列的触发规则多用于流水线整体以独立模式构建BuildAllTestsAsStandalone之外的显式指定。两者的产物形态相似都是独立 Exe差异在于是否由规则自动决定。IsMergedTestRunnerAssembly这是合并运行器本体的标记与RequiresProcessIsolation面向被合并的普通测试恰好相反但它通过DiscoverProcessIsolatedTestProjectReferences与进程隔离测试产生协作——运行器负责发现、编排并逐进程启动这些隔离测试。GCStressIncompatible与HasDisasmCheck注意 CLRTest.Jit.targets 会把两者关联——带 disasm 检查的测试自动视为 GC stress 不兼容实践中这些测试也基本都需要进程隔离。理解这组机制的边界能让你在编写 CoreCLR 测试时准确判断该测试能不能被合并从而既享受共享运行器的性能收益又避免进程级状态污染导致的间歇性失败。若你的测试出现了单独运行通过、合并运行偶发失败的现象优先回到本文的 30 条规则中排查通常都能找到对应的隔离依据。【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考