From c115a86f20924fdbde89ed85c307b122958c2252 Mon Sep 17 00:00:00 2001 From: Ahbong Chang Date: Thu, 17 Oct 2024 07:01:27 +0800 Subject: [PATCH] fix(coverage): Expand external local repo in coverage report (#418) Rules under repo local_repository() are external repo but symlinked to a local path, usually in the same workspace. No matter if the local path is in the same workspace, the users should usually want to check the local path specified in local_repository rules instead of the path in bazel output (`/bazel-/external/...`) in the file explorer. Works towards #416. Improves #362. --- src/bazel/tasks.ts | 16 ++++++++-------- src/test-explorer/index.ts | 4 ++++ src/test-explorer/lcov_parser.ts | 32 +++++++++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/bazel/tasks.ts b/src/bazel/tasks.ts index 21e44965..a9650ab0 100644 --- a/src/bazel/tasks.ts +++ b/src/bazel/tasks.ts @@ -156,10 +156,12 @@ async function onTaskProcessEnd(event: vscode.TaskProcessEndEvent) { if (taskDefinition.command === "coverage" && rawExitCode === 0) { // Find the coverage file and load it. const workspaceInfo = await getWorkspaceInfoFromTask(task.scope); - const outputPath = await new BazelInfo( + const bazelInfo = new BazelInfo( getDefaultBazelExecutablePath(), workspaceInfo.bazelWorkspacePath, - ).getOne("output_path"); + ); + const outputPath = await bazelInfo.getOne("output_path"); + const executionRoot = await bazelInfo.getOne("execution_root"); // Build a description string which will be displayed as part of the test run. const execution = task.execution as vscode.ShellExecution; @@ -183,12 +185,10 @@ async function onTaskProcessEnd(event: vscode.TaskProcessEndEvent) { "the instrumentation filters are set correctly.", ); } else { - // Show the coverage date - await showLcovCoverage( - description, - workspaceInfo.bazelWorkspacePath, - covFileStr, - ); + // The `bazel coverage` runs the build/test/coverage in sandboxes with + // the similar/same layout of execution root, thus using it as the base + // for mapping the source files. + await showLcovCoverage(description, executionRoot, covFileStr); } } catch (e: any) { // eslint-disable-next-line @typescript-eslint/no-floating-promises diff --git a/src/test-explorer/index.ts b/src/test-explorer/index.ts index 031a075c..0bff9c37 100644 --- a/src/test-explorer/index.ts +++ b/src/test-explorer/index.ts @@ -30,6 +30,10 @@ export function activateTesting(): vscode.Disposable[] { /** * Display coverage information from a `.lcov` file. + * + * @param description The heading message for test (coverage) run. + * @param baseFolder The source file entries are relative paths to baseFolder. + * @param lcov The lcov report data in string. */ export async function showLcovCoverage( description: string, diff --git a/src/test-explorer/lcov_parser.ts b/src/test-explorer/lcov_parser.ts index 8c7afa7c..7e850367 100644 --- a/src/test-explorer/lcov_parser.ts +++ b/src/test-explorer/lcov_parser.ts @@ -1,4 +1,5 @@ import * as vscode from "vscode"; +import * as fs from "fs/promises"; import * as path from "path"; import * as child_process from "child_process"; import * as which from "which"; @@ -128,6 +129,32 @@ async function demangleNameUsingFilter( return unmangled; } +async function resolveSourceFilePath( + baseFolder: string, + sfPath: string, +): Promise { + // Ignore and keep the not existing paths for matching in later + // phases. + const resolvedSfPath = path.resolve(baseFolder, sfPath); + // Path could be in pattern `external//...`, + // Try resolve the symlink for /external/, + // so that the SF path could be patched into `////...`. + const externalRepoMatch = sfPath.match(/^external\/([^/]+)(\/.*)/); + if (externalRepoMatch) { + const repoName = externalRepoMatch[1]; + const rest = externalRepoMatch[2]; + try { + const repoPath = await fs.realpath(`${baseFolder}/external/${repoName}`); + const realSourcePath = `${repoPath}${rest}`; + await fs.stat(realSourcePath); + return realSourcePath; + } catch { + // Ignore invalid paths and fallback to original resolved one. + } + } + return resolvedSfPath; +} + /** * Coverage data from a Bazel run. * @@ -154,6 +181,9 @@ export class BazelFileCoverage extends vscode.FileCoverage { /** * Parses the LCOV coverage info into VS Code's representation + * + * @param baseFolder The source file entries are relative paths to baseFolder. + * @param lcov The lcov report data in string. */ export async function parseLcov( baseFolder: string, @@ -203,7 +233,7 @@ export async function parseLcov( if (info !== undefined) { throw new Error(`Duplicated SF entry`); } - const filename = path.resolve(baseFolder, value); + const filename = await resolveSourceFilePath(baseFolder, value); if (!infosByFile.has(filename)) { infosByFile.set(filename, new FileCoverageInfo()); }