From ea837a0db944a14983ef56fc5c451f255d3137bf Mon Sep 17 00:00:00 2001 From: Colin Ihrig Date: Mon, 22 Jul 2024 00:26:43 -0400 Subject: [PATCH] test_runner: refactor coverage to pass in config options This commit updates the test runner's code coverage so that coverage options are explicitly passed in instead of pulled from command line options. PR-URL: https://github.com/nodejs/node/pull/53931 Refs: https://github.com/nodejs/node/issues/53924 Refs: https://github.com/nodejs/node/issues/53867 Refs: https://github.com/nodejs/node/pull/53866 Reviewed-By: Matteo Collina Reviewed-By: Moshe Atlow Reviewed-By: Chemi Atlow Reviewed-By: James M Snell --- lib/internal/test_runner/coverage.js | 120 ++++++++++++++------------- lib/internal/test_runner/harness.js | 2 +- 2 files changed, 62 insertions(+), 60 deletions(-) diff --git a/lib/internal/test_runner/coverage.js b/lib/internal/test_runner/coverage.js index 23f479260292e7..b97965235e7d47 100644 --- a/lib/internal/test_runner/coverage.js +++ b/lib/internal/test_runner/coverage.js @@ -29,16 +29,11 @@ const { tmpdir } = require('os'); const { join, resolve, relative, matchesGlob } = require('path'); const { fileURLToPath } = require('internal/url'); const { kMappings, SourceMap } = require('internal/source_map/source_map'); -const { parseCommandLine } = require('internal/test_runner/utils'); const kCoverageFileRegex = /^coverage-(\d+)-(\d{13})-(\d+)\.json$/; const kIgnoreRegex = /\/\* node:coverage ignore next (?\d+ )?\*\//; const kLineEndingRegex = /\r?\n$/u; const kLineSplitRegex = /(?<=\r?\n)/u; const kStatusRegex = /\/\* node:coverage (?enable|disable) \*\//; -const { - coverageExcludeGlobs, - coverageIncludeGlobs, -} = parseCommandLine(); class CoverageLine { constructor(line, startOffset, src, length = src?.length) { @@ -55,10 +50,12 @@ class CoverageLine { } class TestCoverage { - constructor(coverageDirectory, originalCoverageDirectory, workingDirectory) { + constructor(coverageDirectory, originalCoverageDirectory, workingDirectory, excludeGlobs, includeGlobs) { this.coverageDirectory = coverageDirectory; this.originalCoverageDirectory = originalCoverageDirectory; this.workingDirectory = workingDirectory; + this.excludeGlobs = excludeGlobs; + this.includeGlobs = includeGlobs; } #sourceLines = new SafeMap(); @@ -313,7 +310,7 @@ class TestCoverage { const coverageFile = join(this.coverageDirectory, entry.name); const coverage = JSONParse(readFileSync(coverageFile, 'utf8')); - mergeCoverage(result, this.mapCoverageWithSourceMap(coverage), this.workingDirectory); + this.mergeCoverage(result, this.mapCoverageWithSourceMap(coverage)); } return ArrayFrom(result.values()); @@ -336,7 +333,7 @@ class TestCoverage { const script = result[i]; const { url, functions } = script; - if (shouldSkipFileCoverage(url, this.workingDirectory) || sourceMapCache[url] == null) { + if (this.shouldSkipFileCoverage(url) || sourceMapCache[url] == null) { newResult.set(url, script); continue; } @@ -412,6 +409,54 @@ class TestCoverage { return MathMin(lines[line].startOffset + entry.originalColumn, lines[line].endOffset); } + mergeCoverage(merged, coverage) { + for (let i = 0; i < coverage.length; ++i) { + const newScript = coverage[i]; + const { url } = newScript; + + if (this.shouldSkipFileCoverage(url)) { + continue; + } + + const oldScript = merged.get(url); + + if (oldScript === undefined) { + merged.set(url, newScript); + } else { + mergeCoverageScripts(oldScript, newScript); + } + } + } + + shouldSkipFileCoverage(url) { + // This check filters out core modules, which start with 'node:' in + // coverage reports, as well as any invalid coverages which have been + // observed on Windows. + if (!StringPrototypeStartsWith(url, 'file:')) return true; + + const absolutePath = fileURLToPath(url); + const relativePath = relative(this.workingDirectory, absolutePath); + + // This check filters out files that match the exclude globs. + if (this.excludeGlobs?.length > 0) { + for (let i = 0; i < this.excludeGlobs.length; ++i) { + if (matchesGlob(relativePath, this.excludeGlobs[i]) || + matchesGlob(absolutePath, this.excludeGlobs[i])) return true; + } + } + + // This check filters out files that do not match the include globs. + if (this.includeGlobs?.length > 0) { + for (let i = 0; i < this.includeGlobs.length; ++i) { + if (matchesGlob(relativePath, this.includeGlobs[i]) || + matchesGlob(absolutePath, this.includeGlobs[i])) return false; + } + return true; + } + + // This check filters out the node_modules/ directory, unless it is explicitly included. + return StringPrototypeIncludes(url, '/node_modules/'); + } } function toPercentage(covered, total) { @@ -422,7 +467,7 @@ function sortCoverageFiles(a, b) { return StringPrototypeLocaleCompare(a.path, b.path); } -function setupCoverage() { +function setupCoverage(options) { let originalCoverageDirectory = process.env.NODE_V8_COVERAGE; const cwd = process.cwd(); @@ -446,7 +491,13 @@ function setupCoverage() { // child processes. process.env.NODE_V8_COVERAGE = coverageDirectory; - return new TestCoverage(coverageDirectory, originalCoverageDirectory, cwd); + return new TestCoverage( + coverageDirectory, + originalCoverageDirectory, + cwd, + options.coverageExcludeGlobs, + options.coverageIncludeGlobs, + ); } function mapRangeToLines(range, lines) { @@ -490,55 +541,6 @@ function mapRangeToLines(range, lines) { return { __proto__: null, lines: mappedLines, ignoredLines }; } -function shouldSkipFileCoverage(url, workingDirectory) { - // This check filters out core modules, which start with 'node:' in - // coverage reports, as well as any invalid coverages which have been - // observed on Windows. - if (!StringPrototypeStartsWith(url, 'file:')) return true; - - const absolutePath = fileURLToPath(url); - const relativePath = relative(workingDirectory, absolutePath); - - // This check filters out files that match the exclude globs. - if (coverageExcludeGlobs?.length > 0) { - for (let i = 0; i < coverageExcludeGlobs.length; ++i) { - if (matchesGlob(relativePath, coverageExcludeGlobs[i]) || - matchesGlob(absolutePath, coverageExcludeGlobs[i])) return true; - } - } - - // This check filters out files that do not match the include globs. - if (coverageIncludeGlobs?.length > 0) { - for (let i = 0; i < coverageIncludeGlobs.length; ++i) { - if (matchesGlob(relativePath, coverageIncludeGlobs[i]) || - matchesGlob(absolutePath, coverageIncludeGlobs[i])) return false; - } - return true; - } - - // This check filters out the node_modules/ directory, unless it is explicitly included. - return StringPrototypeIncludes(url, '/node_modules/'); -} - -function mergeCoverage(merged, coverage, workingDirectory) { - for (let i = 0; i < coverage.length; ++i) { - const newScript = coverage[i]; - const { url } = newScript; - - if (shouldSkipFileCoverage(url, workingDirectory)) { - continue; - } - - const oldScript = merged.get(url); - - if (oldScript === undefined) { - merged.set(url, newScript); - } else { - mergeCoverageScripts(oldScript, newScript); - } - } -} - function mergeCoverageScripts(oldScript, newScript) { // Merge the functions from the new coverage into the functions from the // existing (merged) coverage. diff --git a/lib/internal/test_runner/harness.js b/lib/internal/test_runner/harness.js index fbe166b68ee494..02b3f9933d02d6 100644 --- a/lib/internal/test_runner/harness.js +++ b/lib/internal/test_runner/harness.js @@ -95,7 +95,7 @@ function configureCoverage(rootTest, globalOptions) { const { setupCoverage } = require('internal/test_runner/coverage'); try { - return setupCoverage(); + return setupCoverage(globalOptions); } catch (err) { const msg = `Warning: Code coverage could not be enabled. ${err}`;