Skip to content

Commit

Permalink
Fix stdlib coverage paths
Browse files Browse the repository at this point in the history
  • Loading branch information
firelizzard18 committed Nov 16, 2024
1 parent 83f62c7 commit 1ac43af
Showing 1 changed file with 52 additions and 13 deletions.
65 changes: 52 additions & 13 deletions src/test/coverage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import path from 'node:path';
import cp from 'node:child_process';
import { promisify } from 'node:util';
import { Location, Range, StatementCoverage, Uri } from 'vscode';
import { Context } from './testing';
import { Module, RootItem } from './item';
Expand All @@ -11,6 +13,17 @@ import { Module, RootItem } from './item';
* @returns Statement coverage information.
*/
export async function parseCoverage(context: Context, scope: RootItem, coverageFile: Uri) {
// Resolve GOROOT and GOMODCACHE
const { binPath } = context.go.settings.getExecutionCommand('go') || {};
if (!binPath) {
throw new Error('Failed to run "go env" as the "go" binary cannot be found in either GOROOT or PATH');
}

const env = {
GOROOT: await getEnv(binPath, 'GOROOT'),
GOMODCACHE: await getEnv(binPath, 'GOMODCACHE'),
};

const lines = Buffer.from(await context.workspace.fs.readFile(coverageFile))
.toString('utf-8')
.split('\n');
Expand All @@ -24,7 +37,7 @@ export async function parseCoverage(context: Context, scope: RootItem, coverageF
// the actual file path (either absolute or starting with .)
// See https://golang.org/issues/40251.

const parse = parseLine(scope, line);
const parse = parseLine(env, scope, line);
if (!parse) continue;

const statements = coverage.get(`${parse.location.uri}`) || [];
Expand All @@ -35,15 +48,25 @@ export async function parseCoverage(context: Context, scope: RootItem, coverageF
return coverage;
}

async function getEnv(binPath: string, name: string) {
const { stdout } = await promisify(cp.execFile)(binPath, ['env', name]);
return stdout.trim();
}

// Derived from https://golang.org/cl/179377

interface Env {
GOROOT: string;
GOMODCACHE: string;
}

/**
* Parses a line in a coverage file.
* @param scope - The module or workspace for resolving relative paths.
* @param s - The line.
* @returns The parsed line.
*/
function parseLine(scope: RootItem, s: string) {
function parseLine(env: Env, scope: RootItem, s: string) {
/**
* Finds the last occurrence of {@link sep} in {@link s}, splits {@link s}
* on that index, and returns the RHS parsed as a number.
Expand Down Expand Up @@ -80,20 +103,36 @@ function parseLine(scope: RootItem, s: string) {
return;
}

// If it's a relative file path, convert it to an absolute path. From now
// on, we can assume that it's a real file name if it is an absolute path.
let filename = s;
if (filename.startsWith('.' + path.sep)) {
filename = path.resolve(filename, scope.dir.fsPath);
const filename = resolveCoveragePath(env, scope, s);
const range = new Range(startLine, startCol, endLine, endCol);
const location = new Location(Uri.file(filename), range);
return { location, count, statements };
}

function resolveCoveragePath(env: Env, scope: RootItem, filename: string) {
// If it's an absolute path, assume it's correct
if (path.isAbsolute(filename)) {
return filename;
}

// If it's a relative path, convert it to an absolute path and return
if (filename.startsWith(`.${path.sep}`)) {
return path.resolve(filename, scope.dir.fsPath);
}

// If the 'filename' is the package path + file, convert that to a real
// path, e.g. example.com/foo/bar.go -> /home/user/src/foo/bar.go.
// If the scope is a module and the file belongs to it, convert the filepath
// to a real path, e.g. example.com/foo/bar.go -> /home/user/src/foo/bar.go.
if (scope instanceof Module && filename.startsWith(`${scope.path}/`)) {
filename = path.join(scope.dir.fsPath, filename.substring(scope.path.length + 1));
return path.join(scope.dir.fsPath, filename.substring(scope.path.length + 1));
}

const range = new Range(startLine, startCol, endLine, endCol);
const location = new Location(Uri.file(filename), range);
return { location, count, statements };
// If the first segment of the path contains a dot, assume it's a module
const [first] = filename.split(/\\|\//);
if (first.includes('.')) {
// TODO: Resolve the version
return path.join(env.GOMODCACHE, filename);
}

// If the first segment does not contain a dot, assume it's a stdlib package
return path.join(env.GOROOT, 'src', filename);
}

0 comments on commit 1ac43af

Please sign in to comment.