Cookingall-in-one 调试镜像 📅 2026/6/29 11:13:53 核心代码代码的原理很简单dotnet-coverage collect --session-id ${sessionID} cmd: 通过 dotnet-coverage collect 来启动程序并且创建一个 session iddotnet-coverage snapshot --output ${coverageFile} ${sessionID}: 运行期通过 session id 采集 coverage 信息到一个文件dotnet-coverage merge {coverageFile} --output {coberturaFile} --output-format cobertura: 把 coverage 文件转换为 xml 格式reportgenerator -reports:{coberturaFile} -targetdir:{htmlDir} -reporttypes:Html: 把 xml 文件渲染为 htmlpublic static async Task CodeCoverageCallbackAsync(HttpContext ctx) { var sessionId ctx.Request.Query[session-id].FirstOrDefault(); if (string.IsNullOrEmpty(sessionId)) { ctx.Response.StatusCode 400; await ctx.Response.WriteAsync(HtmlError(Missing query parameter: session-id)); return; } var uuid Guid.NewGuid().ToString(N); var coverageFile $/tmp/{uuid}.coverage; var coberturaFile $/tmp/{uuid}.cobertura.xml; var htmlDir $/tmp/html_{uuid}/; // Step 1: snapshot var (snapshotOk, snapshotErr) await RunCommandAsync( dotnet-coverage, $snapshot --output {coverageFile} {sessionId}); if (!snapshotOk) { ctx.Response.StatusCode 500; ctx.Response.ContentType text/html; charsetutf-8; await ctx.Response.WriteAsync(HtmlError($dotnet-coverage snapshot failed:\n{snapshotErr})); return; } // Step 2: convert to cobertura xml var (mergeOk, mergeErr) await RunCommandAsync( dotnet-coverage, $merge {coverageFile} --output {coberturaFile} --output-format cobertura); if (!mergeOk) { ctx.Response.StatusCode 500; ctx.Response.ContentType text/html; charsetutf-8; await ctx.Response.WriteAsync(HtmlError($dotnet-coverage merge failed:\n{mergeErr})); return; } // Step 3: generate html report var (reportOk, reportErr) await RunCommandAsync( reportgenerator, $-reports:{coberturaFile} -targetdir:{htmlDir} -reporttypes:Html); if (!reportOk) { ctx.Response.StatusCode 500; ctx.Response.ContentType text/html; charsetutf-8; await ctx.Response.WriteAsync(HtmlError($reportgenerator failed:\n{reportErr})); return; } ctx.Response.Redirect($/code_coverage_report/{uuid}/); }具体代码请看QiWa.DemoServer/src/CodeCoverage/CodeCoverage.cs at main · ahfuzhang/QiWa.DemoServer · GitHub编译 debug 版本的 dlldotnet publish $(PRJ).csproj \ -r linux-x64 \ -p:DefineConstantsUNIX -p:AllowUnsafeBlockstrue \ -p:StripSymbolsfalse \ -p:InvariantGlobalizationtrue \ -p:EventSourceSupporttrue \ -p:EmbedAllSourcestrue \ -p:DebugTypeportable \ -p:DebugSymbolstrue \ -p:Optimizefalse \ -p:CopyOutputSymbolsToPublishDirectorytrue \ -p:TieredCompilationfalse \ --self-contained false \ -c Debug -o $(BUILD_DIR)特别要注意不要加这些选项PublishAottrue : 触发 AOT 原生编译的核心标志StaticLinkedRuntimetrue / StaticExecutabletrue — 静态链接原生可执行文件PositionIndependentExecutabletrue — 仅对原生二进制有意义--self-contained true: 输出产物将为托管 DLL依赖系统安装的 .NET 运行时。为了保障工具集的版本一致性也可以使用 docker 中的命令来编译:docker run --rm \ -v ./:/app \ -w /app \ --user $(id -u):$(id -g) \ ahfuzhang/csharp-dbg-all-in-one:dotnet10 \ dotnet publish $(PRJ).csproj \ -r linux-x64 \ -p:DefineConstantsUNIX -p:AllowUnsafeBlockstrue \ -p:StripSymbolsfalse \ -p:InvariantGlobalizationtrue \ -p:EventSourceSupporttrue \ -p:EmbedAllSourcestrue \ -p:DebugTypeportable \ -p:DebugSymbolstrue \ -p:Optimizefalse \ -p:CopyOutputSymbolsToPublishDirectorytrue \ -p:TieredCompilationfalse \ --self-contained false \ -c Debug -o $(BUILD_DIR)在 all-in-one 镜像中启动服务器docker run --rm \ -v ./:/app \ -w /app \ --user $(id -u):$(id -g) \ --network host \ ahfuzhang/csharp-dbg-all-in-one:dotnet10 \ dotnet-coverage collect \ --session-id my-server-cov \ --output /tmp/demo_server.coverage \ dotnet $(BUILD_DIR)/QiWa.DemoServer.dll -- \ -log.leveldebug注意:要使用dotnet-coverage collect来启动--session-id my-server-cov这个参数指定了 session id 的名字叫做my-server-cov要记住这个名字dotnet xx.dll保障了程序使用 dotnet CLR 。如果静态编译的话可能就无法采集了--这个后面是服务器的命令行参数通过浏览器查看代码覆盖情况打开浏览器访问http://127.0.0.1:8091/code_coverage?session-idmy-server-cov注意要带上my-server-cov这个名字会触发后端运行命令并生成 html 报告