在 Windows 上构建与运行 CoreCLR 测试:runtime 仓库src\tests完整实战指南
【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime
导读
本文聚焦 .NET runtime 仓库(CoreCLR 运行时)在 Windows 平台上的测试工作流:如何通过src\tests\build.cmd构建托管/原生测试组件、按优先级或子集筛选构建范围、生成 Core_Root 测试布局,并通过src\tests\run.cmd执行测试、定位失败原因。读完本文,你将掌握从"改一行测试代码"到"在本地 Windows 机器上完成单测复现"的完整闭环,并能结合仓库源码理解每一步背后的参数解析与产物目录规则。
前置准备:先构建运行时与库
在构建 CoreCLR 测试之前,需要先构建 runtime(clr子集)与 libraries(libs子集)。从源码结构看,测试构建产物与 CoreCLR 产品二进制、库程序集之间是强依赖关系:build.cmd生成 Core_Root 时会同时拷贝 coreclr 产品二进制与库程序集。若库未按预期配置构建,可通过/p:LibrariesConfiguration指定与默认Release不同的配置。
构建测试:入口脚本与默认行为
构建 CoreCLR 测试必须使用专用脚本,而不是dotnet test:
src\tests\build.cmd这一点在 src/tests/README.md 中被反复强调:此测试树驱动方式不同于dotnet test,错误工具会静默报告 "0 test projects" 或找不到 testhost。
脚本默认架构为x64、配置为Debug、目标 OS 为windows,这些默认值直接定义在 src/tests/build.cmd 中。默认情况下库使用Release配置;如需切换,通过 MSBuild 属性指定:
src\tests\build.cmd /p:LibrariesConfiguration=Debug注意LibrariesConfiguration是直接透传给 MSBuild 的参数(build.cmd会把未识别参数原样传给 MSBuild,见 src/tests/build.cmd),因此必须带/p:前缀。同理,如果宿主(coreclr 产品)使用了不同的构建配置,可额外用/p:HostConfiguration指定。
其他常用顶层参数(均来自 src/tests/build.cmd 的参数解析逻辑):
| 参数 | 作用 | 默认值 |
|---|---|---|
x64/x86/arm64/wasm | 构建架构 | x64 |
debug/checked/release | 构建配置 | Debug |
os <value> | 目标 OS(如 linux、osx、browser、wasi) | windows |
-Rebuild | 清理测试产物后重建 | 关 |
-SkipRestorePackages | 跳过包还原 | 关 |
-SkipManaged | 跳过托管测试构建 | 关 |
-SkipNative | 跳过原生测试构建 | 关 |
-SkipGenerateLayout | 跳过 Core_Root 布局生成 | 关 |
-GenerateLayoutOnly | 仅生成 Core_Root 布局 | 关 |
-MSBuild | 使用 MSBuild 代替默认的 Ninja | 关 |
-Crossgen2 | 用 Crossgen2 预编译 coreroot 中的框架程序集 | 关 |
-Composite | 使用 Crossgen2 复合模式(整个框架编译为单个 R2R 库) | 关 |
-PDB | 预编译时生成 PDB | 关 |
-Perfmap | Crossgen2 编译时输出 perfmap 符号 | 关 |
-NativeAOT | 按 Native AOT 编译方式构建测试 | 关 |
-Mono/-CoreCLR | 选择运行时口味 | CoreCLR |
-fsanitize <s> | 用指定原生 sanitizer 构建原生测试组件 | 无 |
-- | 之后所有参数直接透传 MSBuild | — |
仅构建原生测试组件
当你只想构建原生(native)测试组件而不构建托管部分时,传递skipmanaged与skipgeneratelayout:
src\tests\build.cmd skipmanaged skipgeneratelayout从源码实现看,skipmanaged会跳过托管测试构建,skipgeneratelayout会跳过 Core_Root 布局生成(见 src/tests/build.cmd),二者组合后脚本只执行原生部分:调用eng\native\gen-buildsys.cmd生成 CMake 工程(默认走 Ninja),再执行cmake --build ... --target install完成原生组件安装(见 src/tests/build.cmd)。因为原生测试组件总体构建代价不大,官方建议在任何托管测试构建之前先完整执行一次原生构建。
C++/CLI 原生测试组件:使用 live ref assemblies
默认情况下,C++/CLI 原生测试组件基于仓库根目录 global.json 指定的 SDK 中的 ref pack 构建。若希望改为针对本次构建产出的 ref assemblies 编译,向测试构建传递 CMake 参数:
src\tests\build.cmd skipmanaged -cmakeargs -DCPP_CLI_LIVE_REF_ASSEMBLIES=1在 src/tests/build.cmd 中,-cmakeargs被解析为CMakeArgs并拼入__CMakeArgs,随后在原生构建阶段随gen-buildsys.cmd一并传入 CMake,最终以-DCMAKE_BUILD_RUNTIME_FLAVOR=CoreCLR等参数一起生效(见 src/tests/build.cmd)。
构建预编译(Crossgen)测试
src\tests\build.cmd crossgen该命令使用crossgen.exe在测试可执行文件运行前将其预编译(即 ReadyToRun 化)。注意脚本中实际存在的是crossgen2(见 src/tests/build.cmd),它设置__TestBuildMode=crossgen2,会预编译 coreroot 中的框架托管程序集;可配合-PDB生成 PDB、-Perfmap生成符号映射。构建完成后,coreroot 内即包含预编译产物,运行测试时会直接执行 R2R 镜像。
按优先级构建测试
src\tests\build.cmd -priority=1优先级语义是累积式的:-priority=1会同时构建CLRTestPriority为0和1的测试。默认优先级为0。该逻辑在 src/tests/Directory.Build.props 中实现:命令行指定的__Priority被映射为 MSBuild 属性CLRTestPriorityToBuild,满足CLRTestPriority <= CLRTestPriorityToBuild的测试才会被构建(666表示构建全部)。测试自身的优先级在各自.csproj中通过<CLRTestPriority>设置,未显式声明时默认为 0,详见 docs/workflow/testing/coreclr/test-configuration.md。
生成 Core_Root
src\tests\build.cmd会生成 Core_Root 目录,其中包含运行测试所需的测试宿主corerun、库程序集以及 coreclr 产品二进制。若只想生成 Core_Root 而不构建测试:
src\tests\build.cmd generatelayoutonly输出位于:
<repo_root>\artifacts\tests\coreclr\windows.<arch>.<configuration>\Tests\Core_Root例如 x64 Checked 配置为:<repo_root>\artifacts\tests\coreclr\windows.x64.Checked\Tests\Core_Root。
从 src/tests/build.cmd 可见,generatelayoutonly会同时置位SkipManaged与SkipNative并跳过原生拷贝,仅执行布局生成阶段。布局生成要求先构建过libs子集;若库不是Release配置,需同步传入/p:LibrariesConfiguration=<config>,否则布局中的库程序集会与预期不符。Core_Root 也是运行单个测试(见下文)所必需的环境,可通过CORE_ROOT环境变量或-coreroot参数引用。
常用组合示例
:: 构建 crossgen 预编译的 priority 0 和 1 测试 src\tests\build.cmd crossgen -priority=1 :: 为 x86 release 仅生成 Core_Root,不构建测试 src\tests\build.cmd x86 Release generatelayoutonly查看build.cmd支持的全部参数:
src\tests\build.cmd -?脚本内置的-TestArgParsing开关还可以在不真正构建的情况下打印参数解析结果,便于排查参数拼写问题(见 src/tests/build.cmd)。
构建测试子集:test/dir/tree
不指定任何子集时,src\tests下的整棵测试树都会被构建,这在-priority=1模式下非常耗时。build.cmd提供三种限定范围的选项,且均可重复指定或一次传多个以分号分隔的路径:
1)test <test-project>—— 构建指定测试项目(路径可为绝对路径或相对src\tests的路径):
src\tests\build.cmd test JIT/Methodical/divrem/div/i4div_cs_do.csproj;JIT/Methodical/divrem/div/i8div_cs_do.csproj(仓库当前该目录下实际以源码文件i4div.cs、i8div.cs等组织,见 src/tests/JIT/Methodical/divrem/div;项目文件位于上层聚合目录。)
2)dir <test-folder>—— 构建指定目录下的全部测试项目:
src\tests\build.cmd dir JIT/Methodical/Arrays/huge;JIT/Methodical/divrem/div3)tree <root-folder>—— 构建指定子树根路径下的全部测试项目:
src\tests\build.cmd tree baseservices/exceptions;JIT/Methodical参数解析实现在 src/tests/build.cmd:test/dir/tree各自累积到__BuildTestProject/__BuildTestDir/__BuildTestTree,最终作为 MSBuild 属性参与构建。
重要提醒:优先级过滤与子集选择是正交的。即使你指定构建某个具体测试,只要它是 Pri1 且命令行未带-priority=1,该测试就会被跳过——从 src/tests/Directory.Build.props 的实现看,优先级过滤发生在子集确定之后。这是当前设计(官方希望长期内最终移除优先级机制),因此务必记得为 Pri1+ 测试显式补充-priority。
构建单个测试
前置条件:如果单个测试带原生资源,需要先至少执行一次build.cmd skipmanaged [其他参数]。
- 原生测试:构建与测试 cmake 文件对应的生成的 Visual Studio 解决方案或 makefile。
- 托管测试:直接使用仓库根目录的
dotnet.cmd对测试项目执行构建:
dotnet.cmd build -c <Configuration> src\tests\path\to\test.csproj除测试程序集外,构建还会在测试输出目录中为测试生成一个.cmd脚本(即测试入口点)。测试输出目录位于<repo_root>\artifacts\tests\coreclr\windows.<arch>.<configuration>之下、按测试在源码中的位置对应的子路径中,例如带原生组件的 src/tests/Exceptions/ForeignThread/ForeignThreadExceptions.csproj 这类项目会在对应目录生成ForeignThreadExceptions.cmd。
运行全部测试
run.cmd支持的所有参数可以通过帮助查看:
src\tests\run.cmd /?使用 Checked 构建运行全部测试:
src\tests\run.cmd checked运行完成后会生成名为TestRun_<arch>_<flavor>.html的报告(例如TestRun_windows_x64_Checked.html),位于<repo_root>\artifacts\log子目录;所有失败测试会列在TestRunResults_windows_x64_Checked.err中。
从 src/tests/run.cmd 的解析逻辑看,run.cmd支持架构(x64/x86/arm64/wasm)与配置(debug/release/checked)位置参数,此外还提供大量实用开关:
| 开关 | 作用 |
|---|---|
jitstress <n> | 以DOTNET_JitStress=n运行 |
jitstressregs <n> | 以DOTNET_JitStressRegs=n运行 |
jitminopts | 以DOTNET_JITMinOpts=1运行 |
jitforcerelocs | 以DOTNET_ForceRelocs=1运行 |
gcname <name> | 以DOTNET_GCName=name运行 |
gcstresslevel <n> | 以DOTNET_GCStress=n运行(n 为位掩码:1=所有分配处 GC,2=进入抢占式 GC 时,4=每个可 JIT 指令,8=每个可 NGEN 指令,16=仅唯一栈路径;须以十六进制表达) |
gcsimulator | 运行 GC Simulator 测试 |
longgc | 运行长时间 GC 测试 |
ilasmroundtrip | 对测试执行 ilasm 往返 |
timeout <n> | 单测试超时毫秒数(默认 600000,即 10 分钟;部分开关会覆盖它) |
sequential/parallel <type> | 顺序执行 / 并行级别(none、collections、assemblies、all,默认 collections) |
printlastresultsonly | 不运行测试,仅打印上次结果 |
runincontext | 在可卸载的 AssemblyLoadContext 中运行每个测试 |
tree <path> | 仅运行指定子树下的测试(如JIT/Regression) |
runcrossgen2tests | 运行 Crossgen2 编译的 ReadyToRun 测试 |
interpreter/node | 启用解释器 / 使用 NodeJS(wasm) |
<CORE_ROOT> | 显式指定待测运行时路径 |
run.cmd最终会把参数转发给src\tests\run.py执行(见 src/tests/run.cmd)。
调查测试失败
测试运行结束后可能有一个或多个测试失败。测试输出位于 Reports 目录,默认形如:
<repo_root>\artifacts\tests\coreclr\windows.x64.Checked\Reports\Exceptions\Finalization其中有两个关键文件:
Finalizer.output.txt—— 测试自身记录的全部日志信息;Finalizer.error.txt—— 测试进程崩溃时由 CoreRun.exe 报告的崩溃信息。
要重跑失败的测试,按"运行单个测试"一节操作即可;失败测试的报告会包含其确切的运行命令,例如:
<repo_root>\artifacts\tests\coreclr\windows.x64.Checked\Exceptions\Finalization\Finalizer.cmd这给了你"精确复现"的入口:直接执行该.cmd,或在调试器下执行它定位崩溃点。
运行单个测试
先按上文"构建单个测试"构建目标测试,然后:
- 将
CORE_ROOT环境变量设置为 Core_Root 目录:
set CORE_ROOT=<repo_root>\artifacts\tests\coreclr\windows.x64.Checked\Tests\Core_Root- 运行测试生成的
.cmd脚本:
<test_output_dir>\<TestName>.cmd如果想在调试器(例如 WinDbg)下运行,向测试命令追加-debug <debuggerFullPath>:
<TestName>.cmd -debug C:\path\to\windbg.exe这些.cmd入口脚本的参数与corerun基本一致(-debug、-env、-coreroot),其中-coreroot可替代环境变量CORE_ROOT显式指定运行时布局;相关说明可参考 docs/workflow/testing/using-corerun-and-coreroot.md。
修改测试并复现
当测试需要改动时,流程是:修改测试源码 → 重新构建该测试项目 → 二进制会被 binplace 到测试二进制目录(例如<repo_root>\artifacts\tests\coreclr\windows.x64.Checked\Exceptions\Finalization)→ 按上文方式重新运行。
如果要新增测试,可参考 docs/workflow/testing/coreclr/test-configuration.md 中的创建指南:以现有项目为模板、移除<AssemblyName>、设置<CLRTestKind>/<CLRTestPriority>、通过 XunitFact特性或自定义Main(成功返回100,失败返回非100)编写用例。测试树中大量测试通过<RequiresProcessIsolation>true</RequiresProcessIsolation>标记为独立进程运行,或通过<Import Project="$(TestSourceDir)MergedTestRunner.targets" />归入合并运行器,选择哪个入口取决于测试项目的声明方式——具体判定步骤见 docs/workflow/testing/coreclr/testing.md。
常见问题速查
- 构建测试报 "0 test projects":检查是否遗漏了
-priority=1(针对 Pri1 测试)或误用了dotnet test而非build.cmd/build.sh。 - 运行单个测试找不到运行时:确认
CORE_ROOT已设置或传了-coreroot,且 Core_Root 是通过generatelayoutonly/完整构建生成的有效布局。 - 测试失败但想看崩溃现场:查看
Reports目录下的*.error.txt,并用-debug <debuggerFullPath>重跑。 - C++/CLI 组件链接到旧 ref 程序集:用
-cmakeargs -DCPP_CLI_LIVE_REF_ASSEMBLIES=1切换到本次构建产出的 ref assemblies。 - 希望控制构建/运行范围:构建用
test/dir/tree子集,运行用tree <path>限定,二者都别忘了优先级参数。
【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考