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}`;