11cb0ef41Sopenharmony_ci'use strict'; 21cb0ef41Sopenharmony_ciconst { 31cb0ef41Sopenharmony_ci ArrayFrom, 41cb0ef41Sopenharmony_ci ArrayPrototypeMap, 51cb0ef41Sopenharmony_ci ArrayPrototypePush, 61cb0ef41Sopenharmony_ci JSONParse, 71cb0ef41Sopenharmony_ci MathFloor, 81cb0ef41Sopenharmony_ci NumberParseInt, 91cb0ef41Sopenharmony_ci RegExpPrototypeExec, 101cb0ef41Sopenharmony_ci RegExpPrototypeSymbolSplit, 111cb0ef41Sopenharmony_ci SafeMap, 121cb0ef41Sopenharmony_ci SafeSet, 131cb0ef41Sopenharmony_ci StringPrototypeIncludes, 141cb0ef41Sopenharmony_ci StringPrototypeLocaleCompare, 151cb0ef41Sopenharmony_ci StringPrototypeStartsWith, 161cb0ef41Sopenharmony_ci} = primordials; 171cb0ef41Sopenharmony_ciconst { 181cb0ef41Sopenharmony_ci copyFileSync, 191cb0ef41Sopenharmony_ci mkdirSync, 201cb0ef41Sopenharmony_ci mkdtempSync, 211cb0ef41Sopenharmony_ci opendirSync, 221cb0ef41Sopenharmony_ci readFileSync, 231cb0ef41Sopenharmony_ci} = require('fs'); 241cb0ef41Sopenharmony_ciconst { setupCoverageHooks } = require('internal/util'); 251cb0ef41Sopenharmony_ciconst { tmpdir } = require('os'); 261cb0ef41Sopenharmony_ciconst { join, resolve } = require('path'); 271cb0ef41Sopenharmony_ciconst { fileURLToPath } = require('url'); 281cb0ef41Sopenharmony_ciconst kCoverageFileRegex = /^coverage-(\d+)-(\d{13})-(\d+)\.json$/; 291cb0ef41Sopenharmony_ciconst kIgnoreRegex = /\/\* node:coverage ignore next (?<count>\d+ )?\*\//; 301cb0ef41Sopenharmony_ciconst kLineEndingRegex = /\r?\n$/u; 311cb0ef41Sopenharmony_ciconst kLineSplitRegex = /(?<=\r?\n)/u; 321cb0ef41Sopenharmony_ciconst kStatusRegex = /\/\* node:coverage (?<status>enable|disable) \*\//; 331cb0ef41Sopenharmony_ci 341cb0ef41Sopenharmony_ciclass CoverageLine { 351cb0ef41Sopenharmony_ci #covered; 361cb0ef41Sopenharmony_ci 371cb0ef41Sopenharmony_ci constructor(line, src, startOffset) { 381cb0ef41Sopenharmony_ci const newlineLength = 391cb0ef41Sopenharmony_ci RegExpPrototypeExec(kLineEndingRegex, src)?.[0].length ?? 0; 401cb0ef41Sopenharmony_ci 411cb0ef41Sopenharmony_ci this.line = line; 421cb0ef41Sopenharmony_ci this.src = src; 431cb0ef41Sopenharmony_ci this.startOffset = startOffset; 441cb0ef41Sopenharmony_ci this.endOffset = startOffset + src.length - newlineLength; 451cb0ef41Sopenharmony_ci this.ignore = false; 461cb0ef41Sopenharmony_ci this.#covered = true; 471cb0ef41Sopenharmony_ci } 481cb0ef41Sopenharmony_ci 491cb0ef41Sopenharmony_ci get covered() { 501cb0ef41Sopenharmony_ci return this.#covered; 511cb0ef41Sopenharmony_ci } 521cb0ef41Sopenharmony_ci 531cb0ef41Sopenharmony_ci set covered(isCovered) { 541cb0ef41Sopenharmony_ci // V8 can generate multiple ranges that span the same line. 551cb0ef41Sopenharmony_ci if (!this.#covered) { 561cb0ef41Sopenharmony_ci return; 571cb0ef41Sopenharmony_ci } 581cb0ef41Sopenharmony_ci 591cb0ef41Sopenharmony_ci this.#covered = isCovered; 601cb0ef41Sopenharmony_ci } 611cb0ef41Sopenharmony_ci} 621cb0ef41Sopenharmony_ci 631cb0ef41Sopenharmony_ciclass TestCoverage { 641cb0ef41Sopenharmony_ci constructor(coverageDirectory, originalCoverageDirectory, workingDirectory) { 651cb0ef41Sopenharmony_ci this.coverageDirectory = coverageDirectory; 661cb0ef41Sopenharmony_ci this.originalCoverageDirectory = originalCoverageDirectory; 671cb0ef41Sopenharmony_ci this.workingDirectory = workingDirectory; 681cb0ef41Sopenharmony_ci } 691cb0ef41Sopenharmony_ci 701cb0ef41Sopenharmony_ci summary() { 711cb0ef41Sopenharmony_ci internalBinding('profiler').takeCoverage(); 721cb0ef41Sopenharmony_ci const coverage = getCoverageFromDirectory(this.coverageDirectory); 731cb0ef41Sopenharmony_ci const coverageSummary = { 741cb0ef41Sopenharmony_ci __proto__: null, 751cb0ef41Sopenharmony_ci workingDirectory: this.workingDirectory, 761cb0ef41Sopenharmony_ci files: [], 771cb0ef41Sopenharmony_ci totals: { 781cb0ef41Sopenharmony_ci __proto__: null, 791cb0ef41Sopenharmony_ci totalLineCount: 0, 801cb0ef41Sopenharmony_ci totalBranchCount: 0, 811cb0ef41Sopenharmony_ci totalFunctionCount: 0, 821cb0ef41Sopenharmony_ci coveredLineCount: 0, 831cb0ef41Sopenharmony_ci coveredBranchCount: 0, 841cb0ef41Sopenharmony_ci coveredFunctionCount: 0, 851cb0ef41Sopenharmony_ci coveredLinePercent: 0, 861cb0ef41Sopenharmony_ci coveredBranchPercent: 0, 871cb0ef41Sopenharmony_ci coveredFunctionPercent: 0, 881cb0ef41Sopenharmony_ci }, 891cb0ef41Sopenharmony_ci }; 901cb0ef41Sopenharmony_ci 911cb0ef41Sopenharmony_ci if (!coverage) { 921cb0ef41Sopenharmony_ci return coverageSummary; 931cb0ef41Sopenharmony_ci } 941cb0ef41Sopenharmony_ci 951cb0ef41Sopenharmony_ci for (let i = 0; i < coverage.length; ++i) { 961cb0ef41Sopenharmony_ci const { functions, url } = coverage[i]; 971cb0ef41Sopenharmony_ci 981cb0ef41Sopenharmony_ci // Split the file source into lines. Make sure the lines maintain their 991cb0ef41Sopenharmony_ci // original line endings because those characters are necessary for 1001cb0ef41Sopenharmony_ci // determining offsets in the file. 1011cb0ef41Sopenharmony_ci const filePath = fileURLToPath(url); 1021cb0ef41Sopenharmony_ci let source; 1031cb0ef41Sopenharmony_ci 1041cb0ef41Sopenharmony_ci try { 1051cb0ef41Sopenharmony_ci source = readFileSync(filePath, 'utf8'); 1061cb0ef41Sopenharmony_ci } catch { 1071cb0ef41Sopenharmony_ci // The file can no longer be read. It may have been deleted among 1081cb0ef41Sopenharmony_ci // other possibilities. Leave it out of the coverage report. 1091cb0ef41Sopenharmony_ci continue; 1101cb0ef41Sopenharmony_ci } 1111cb0ef41Sopenharmony_ci 1121cb0ef41Sopenharmony_ci const linesWithBreaks = 1131cb0ef41Sopenharmony_ci RegExpPrototypeSymbolSplit(kLineSplitRegex, source); 1141cb0ef41Sopenharmony_ci let ignoreCount = 0; 1151cb0ef41Sopenharmony_ci let enabled = true; 1161cb0ef41Sopenharmony_ci let offset = 0; 1171cb0ef41Sopenharmony_ci let totalBranches = 0; 1181cb0ef41Sopenharmony_ci let totalFunctions = 0; 1191cb0ef41Sopenharmony_ci let branchesCovered = 0; 1201cb0ef41Sopenharmony_ci let functionsCovered = 0; 1211cb0ef41Sopenharmony_ci 1221cb0ef41Sopenharmony_ci const lines = ArrayPrototypeMap(linesWithBreaks, (line, i) => { 1231cb0ef41Sopenharmony_ci const startOffset = offset; 1241cb0ef41Sopenharmony_ci const coverageLine = new CoverageLine(i + 1, line, startOffset); 1251cb0ef41Sopenharmony_ci 1261cb0ef41Sopenharmony_ci offset += line.length; 1271cb0ef41Sopenharmony_ci 1281cb0ef41Sopenharmony_ci // Determine if this line is being ignored. 1291cb0ef41Sopenharmony_ci if (ignoreCount > 0) { 1301cb0ef41Sopenharmony_ci ignoreCount--; 1311cb0ef41Sopenharmony_ci coverageLine.ignore = true; 1321cb0ef41Sopenharmony_ci } else if (!enabled) { 1331cb0ef41Sopenharmony_ci coverageLine.ignore = true; 1341cb0ef41Sopenharmony_ci } 1351cb0ef41Sopenharmony_ci 1361cb0ef41Sopenharmony_ci if (!coverageLine.ignore) { 1371cb0ef41Sopenharmony_ci // If this line is not already being ignored, check for ignore 1381cb0ef41Sopenharmony_ci // comments. 1391cb0ef41Sopenharmony_ci const match = RegExpPrototypeExec(kIgnoreRegex, line); 1401cb0ef41Sopenharmony_ci 1411cb0ef41Sopenharmony_ci if (match !== null) { 1421cb0ef41Sopenharmony_ci ignoreCount = NumberParseInt(match.groups?.count ?? 1, 10); 1431cb0ef41Sopenharmony_ci } 1441cb0ef41Sopenharmony_ci } 1451cb0ef41Sopenharmony_ci 1461cb0ef41Sopenharmony_ci // Check for comments to enable/disable coverage no matter what. These 1471cb0ef41Sopenharmony_ci // take precedence over ignore comments. 1481cb0ef41Sopenharmony_ci const match = RegExpPrototypeExec(kStatusRegex, line); 1491cb0ef41Sopenharmony_ci const status = match?.groups?.status; 1501cb0ef41Sopenharmony_ci 1511cb0ef41Sopenharmony_ci if (status) { 1521cb0ef41Sopenharmony_ci ignoreCount = 0; 1531cb0ef41Sopenharmony_ci enabled = status === 'enable'; 1541cb0ef41Sopenharmony_ci } 1551cb0ef41Sopenharmony_ci 1561cb0ef41Sopenharmony_ci return coverageLine; 1571cb0ef41Sopenharmony_ci }); 1581cb0ef41Sopenharmony_ci 1591cb0ef41Sopenharmony_ci for (let j = 0; j < functions.length; ++j) { 1601cb0ef41Sopenharmony_ci const { isBlockCoverage, ranges } = functions[j]; 1611cb0ef41Sopenharmony_ci 1621cb0ef41Sopenharmony_ci for (let k = 0; k < ranges.length; ++k) { 1631cb0ef41Sopenharmony_ci const range = ranges[k]; 1641cb0ef41Sopenharmony_ci 1651cb0ef41Sopenharmony_ci mapRangeToLines(range, lines); 1661cb0ef41Sopenharmony_ci 1671cb0ef41Sopenharmony_ci if (isBlockCoverage) { 1681cb0ef41Sopenharmony_ci if (range.count !== 0 || 1691cb0ef41Sopenharmony_ci range.ignoredLines === range.lines.length) { 1701cb0ef41Sopenharmony_ci branchesCovered++; 1711cb0ef41Sopenharmony_ci } 1721cb0ef41Sopenharmony_ci 1731cb0ef41Sopenharmony_ci totalBranches++; 1741cb0ef41Sopenharmony_ci } 1751cb0ef41Sopenharmony_ci } 1761cb0ef41Sopenharmony_ci 1771cb0ef41Sopenharmony_ci if (j > 0 && ranges.length > 0) { 1781cb0ef41Sopenharmony_ci const range = ranges[0]; 1791cb0ef41Sopenharmony_ci 1801cb0ef41Sopenharmony_ci if (range.count !== 0 || range.ignoredLines === range.lines.length) { 1811cb0ef41Sopenharmony_ci functionsCovered++; 1821cb0ef41Sopenharmony_ci } 1831cb0ef41Sopenharmony_ci 1841cb0ef41Sopenharmony_ci totalFunctions++; 1851cb0ef41Sopenharmony_ci } 1861cb0ef41Sopenharmony_ci } 1871cb0ef41Sopenharmony_ci 1881cb0ef41Sopenharmony_ci let coveredCnt = 0; 1891cb0ef41Sopenharmony_ci const uncoveredLineNums = []; 1901cb0ef41Sopenharmony_ci 1911cb0ef41Sopenharmony_ci for (let j = 0; j < lines.length; ++j) { 1921cb0ef41Sopenharmony_ci const line = lines[j]; 1931cb0ef41Sopenharmony_ci 1941cb0ef41Sopenharmony_ci if (line.covered || line.ignore) { 1951cb0ef41Sopenharmony_ci coveredCnt++; 1961cb0ef41Sopenharmony_ci } else { 1971cb0ef41Sopenharmony_ci ArrayPrototypePush(uncoveredLineNums, line.line); 1981cb0ef41Sopenharmony_ci } 1991cb0ef41Sopenharmony_ci } 2001cb0ef41Sopenharmony_ci 2011cb0ef41Sopenharmony_ci ArrayPrototypePush(coverageSummary.files, { 2021cb0ef41Sopenharmony_ci __proto__: null, 2031cb0ef41Sopenharmony_ci path: filePath, 2041cb0ef41Sopenharmony_ci totalLineCount: lines.length, 2051cb0ef41Sopenharmony_ci totalBranchCount: totalBranches, 2061cb0ef41Sopenharmony_ci totalFunctionCount: totalFunctions, 2071cb0ef41Sopenharmony_ci coveredLineCount: coveredCnt, 2081cb0ef41Sopenharmony_ci coveredBranchCount: branchesCovered, 2091cb0ef41Sopenharmony_ci coveredFunctionCount: functionsCovered, 2101cb0ef41Sopenharmony_ci coveredLinePercent: toPercentage(coveredCnt, lines.length), 2111cb0ef41Sopenharmony_ci coveredBranchPercent: toPercentage(branchesCovered, totalBranches), 2121cb0ef41Sopenharmony_ci coveredFunctionPercent: toPercentage(functionsCovered, totalFunctions), 2131cb0ef41Sopenharmony_ci uncoveredLineNumbers: uncoveredLineNums, 2141cb0ef41Sopenharmony_ci }); 2151cb0ef41Sopenharmony_ci 2161cb0ef41Sopenharmony_ci coverageSummary.totals.totalLineCount += lines.length; 2171cb0ef41Sopenharmony_ci coverageSummary.totals.totalBranchCount += totalBranches; 2181cb0ef41Sopenharmony_ci coverageSummary.totals.totalFunctionCount += totalFunctions; 2191cb0ef41Sopenharmony_ci coverageSummary.totals.coveredLineCount += coveredCnt; 2201cb0ef41Sopenharmony_ci coverageSummary.totals.coveredBranchCount += branchesCovered; 2211cb0ef41Sopenharmony_ci coverageSummary.totals.coveredFunctionCount += functionsCovered; 2221cb0ef41Sopenharmony_ci } 2231cb0ef41Sopenharmony_ci 2241cb0ef41Sopenharmony_ci coverageSummary.totals.coveredLinePercent = toPercentage( 2251cb0ef41Sopenharmony_ci coverageSummary.totals.coveredLineCount, 2261cb0ef41Sopenharmony_ci coverageSummary.totals.totalLineCount, 2271cb0ef41Sopenharmony_ci ); 2281cb0ef41Sopenharmony_ci coverageSummary.totals.coveredBranchPercent = toPercentage( 2291cb0ef41Sopenharmony_ci coverageSummary.totals.coveredBranchCount, 2301cb0ef41Sopenharmony_ci coverageSummary.totals.totalBranchCount, 2311cb0ef41Sopenharmony_ci ); 2321cb0ef41Sopenharmony_ci coverageSummary.totals.coveredFunctionPercent = toPercentage( 2331cb0ef41Sopenharmony_ci coverageSummary.totals.coveredFunctionCount, 2341cb0ef41Sopenharmony_ci coverageSummary.totals.totalFunctionCount, 2351cb0ef41Sopenharmony_ci ); 2361cb0ef41Sopenharmony_ci coverageSummary.files.sort(sortCoverageFiles); 2371cb0ef41Sopenharmony_ci 2381cb0ef41Sopenharmony_ci return coverageSummary; 2391cb0ef41Sopenharmony_ci } 2401cb0ef41Sopenharmony_ci 2411cb0ef41Sopenharmony_ci cleanup() { 2421cb0ef41Sopenharmony_ci // Restore the original value of process.env.NODE_V8_COVERAGE. Then, copy 2431cb0ef41Sopenharmony_ci // all of the created coverage files to the original coverage directory. 2441cb0ef41Sopenharmony_ci if (this.originalCoverageDirectory === undefined) { 2451cb0ef41Sopenharmony_ci delete process.env.NODE_V8_COVERAGE; 2461cb0ef41Sopenharmony_ci return; 2471cb0ef41Sopenharmony_ci } 2481cb0ef41Sopenharmony_ci 2491cb0ef41Sopenharmony_ci process.env.NODE_V8_COVERAGE = this.originalCoverageDirectory; 2501cb0ef41Sopenharmony_ci let dir; 2511cb0ef41Sopenharmony_ci 2521cb0ef41Sopenharmony_ci try { 2531cb0ef41Sopenharmony_ci mkdirSync(this.originalCoverageDirectory, { __proto__: null, recursive: true }); 2541cb0ef41Sopenharmony_ci dir = opendirSync(this.coverageDirectory); 2551cb0ef41Sopenharmony_ci 2561cb0ef41Sopenharmony_ci for (let entry; (entry = dir.readSync()) !== null;) { 2571cb0ef41Sopenharmony_ci const src = join(this.coverageDirectory, entry.name); 2581cb0ef41Sopenharmony_ci const dst = join(this.originalCoverageDirectory, entry.name); 2591cb0ef41Sopenharmony_ci copyFileSync(src, dst); 2601cb0ef41Sopenharmony_ci } 2611cb0ef41Sopenharmony_ci } finally { 2621cb0ef41Sopenharmony_ci if (dir) { 2631cb0ef41Sopenharmony_ci dir.closeSync(); 2641cb0ef41Sopenharmony_ci } 2651cb0ef41Sopenharmony_ci } 2661cb0ef41Sopenharmony_ci } 2671cb0ef41Sopenharmony_ci} 2681cb0ef41Sopenharmony_ci 2691cb0ef41Sopenharmony_cifunction toPercentage(covered, total) { 2701cb0ef41Sopenharmony_ci return total === 0 ? 100 : (covered / total) * 100; 2711cb0ef41Sopenharmony_ci} 2721cb0ef41Sopenharmony_ci 2731cb0ef41Sopenharmony_cifunction sortCoverageFiles(a, b) { 2741cb0ef41Sopenharmony_ci return StringPrototypeLocaleCompare(a.path, b.path); 2751cb0ef41Sopenharmony_ci} 2761cb0ef41Sopenharmony_ci 2771cb0ef41Sopenharmony_cifunction setupCoverage() { 2781cb0ef41Sopenharmony_ci let originalCoverageDirectory = process.env.NODE_V8_COVERAGE; 2791cb0ef41Sopenharmony_ci const cwd = process.cwd(); 2801cb0ef41Sopenharmony_ci 2811cb0ef41Sopenharmony_ci if (originalCoverageDirectory) { 2821cb0ef41Sopenharmony_ci // NODE_V8_COVERAGE was already specified. Convert it to an absolute path 2831cb0ef41Sopenharmony_ci // and store it for later. The test runner will use a temporary directory 2841cb0ef41Sopenharmony_ci // so that no preexisting coverage files interfere with the results of the 2851cb0ef41Sopenharmony_ci // coverage report. Then, once the coverage is computed, move the coverage 2861cb0ef41Sopenharmony_ci // files back to the original NODE_V8_COVERAGE directory. 2871cb0ef41Sopenharmony_ci originalCoverageDirectory = resolve(cwd, originalCoverageDirectory); 2881cb0ef41Sopenharmony_ci } 2891cb0ef41Sopenharmony_ci 2901cb0ef41Sopenharmony_ci const coverageDirectory = mkdtempSync(join(tmpdir(), 'node-coverage-')); 2911cb0ef41Sopenharmony_ci const enabled = setupCoverageHooks(coverageDirectory); 2921cb0ef41Sopenharmony_ci 2931cb0ef41Sopenharmony_ci if (!enabled) { 2941cb0ef41Sopenharmony_ci return null; 2951cb0ef41Sopenharmony_ci } 2961cb0ef41Sopenharmony_ci 2971cb0ef41Sopenharmony_ci // Ensure that NODE_V8_COVERAGE is set so that coverage can propagate to 2981cb0ef41Sopenharmony_ci // child processes. 2991cb0ef41Sopenharmony_ci process.env.NODE_V8_COVERAGE = coverageDirectory; 3001cb0ef41Sopenharmony_ci 3011cb0ef41Sopenharmony_ci return new TestCoverage(coverageDirectory, originalCoverageDirectory, cwd); 3021cb0ef41Sopenharmony_ci} 3031cb0ef41Sopenharmony_ci 3041cb0ef41Sopenharmony_cifunction mapRangeToLines(range, lines) { 3051cb0ef41Sopenharmony_ci const { startOffset, endOffset, count } = range; 3061cb0ef41Sopenharmony_ci const mappedLines = []; 3071cb0ef41Sopenharmony_ci let ignoredLines = 0; 3081cb0ef41Sopenharmony_ci let start = 0; 3091cb0ef41Sopenharmony_ci let end = lines.length; 3101cb0ef41Sopenharmony_ci let mid; 3111cb0ef41Sopenharmony_ci 3121cb0ef41Sopenharmony_ci while (start <= end) { 3131cb0ef41Sopenharmony_ci mid = MathFloor((start + end) / 2); 3141cb0ef41Sopenharmony_ci let line = lines[mid]; 3151cb0ef41Sopenharmony_ci 3161cb0ef41Sopenharmony_ci if (startOffset >= line?.startOffset && startOffset <= line?.endOffset) { 3171cb0ef41Sopenharmony_ci while (endOffset > line?.startOffset) { 3181cb0ef41Sopenharmony_ci // If the range is not covered, and the range covers the entire line, 3191cb0ef41Sopenharmony_ci // then mark that line as not covered. 3201cb0ef41Sopenharmony_ci if (count === 0 && startOffset <= line.startOffset && 3211cb0ef41Sopenharmony_ci endOffset >= line.endOffset) { 3221cb0ef41Sopenharmony_ci line.covered = false; 3231cb0ef41Sopenharmony_ci } 3241cb0ef41Sopenharmony_ci 3251cb0ef41Sopenharmony_ci ArrayPrototypePush(mappedLines, line); 3261cb0ef41Sopenharmony_ci 3271cb0ef41Sopenharmony_ci if (line.ignore) { 3281cb0ef41Sopenharmony_ci ignoredLines++; 3291cb0ef41Sopenharmony_ci } 3301cb0ef41Sopenharmony_ci 3311cb0ef41Sopenharmony_ci mid++; 3321cb0ef41Sopenharmony_ci line = lines[mid]; 3331cb0ef41Sopenharmony_ci } 3341cb0ef41Sopenharmony_ci 3351cb0ef41Sopenharmony_ci break; 3361cb0ef41Sopenharmony_ci } else if (startOffset >= line?.endOffset) { 3371cb0ef41Sopenharmony_ci start = mid + 1; 3381cb0ef41Sopenharmony_ci } else { 3391cb0ef41Sopenharmony_ci end = mid - 1; 3401cb0ef41Sopenharmony_ci } 3411cb0ef41Sopenharmony_ci } 3421cb0ef41Sopenharmony_ci 3431cb0ef41Sopenharmony_ci // Add some useful data to the range. The test runner has read these ranges 3441cb0ef41Sopenharmony_ci // from a file, so we own the data structures and can do what we want. 3451cb0ef41Sopenharmony_ci range.lines = mappedLines; 3461cb0ef41Sopenharmony_ci range.ignoredLines = ignoredLines; 3471cb0ef41Sopenharmony_ci} 3481cb0ef41Sopenharmony_ci 3491cb0ef41Sopenharmony_cifunction getCoverageFromDirectory(coverageDirectory) { 3501cb0ef41Sopenharmony_ci const result = new SafeMap(); 3511cb0ef41Sopenharmony_ci let dir; 3521cb0ef41Sopenharmony_ci 3531cb0ef41Sopenharmony_ci try { 3541cb0ef41Sopenharmony_ci dir = opendirSync(coverageDirectory); 3551cb0ef41Sopenharmony_ci 3561cb0ef41Sopenharmony_ci for (let entry; (entry = dir.readSync()) !== null;) { 3571cb0ef41Sopenharmony_ci if (RegExpPrototypeExec(kCoverageFileRegex, entry.name) === null) { 3581cb0ef41Sopenharmony_ci continue; 3591cb0ef41Sopenharmony_ci } 3601cb0ef41Sopenharmony_ci 3611cb0ef41Sopenharmony_ci const coverageFile = join(coverageDirectory, entry.name); 3621cb0ef41Sopenharmony_ci const coverage = JSONParse(readFileSync(coverageFile, 'utf8')); 3631cb0ef41Sopenharmony_ci 3641cb0ef41Sopenharmony_ci mergeCoverage(result, coverage.result); 3651cb0ef41Sopenharmony_ci } 3661cb0ef41Sopenharmony_ci 3671cb0ef41Sopenharmony_ci return ArrayFrom(result.values()); 3681cb0ef41Sopenharmony_ci } finally { 3691cb0ef41Sopenharmony_ci if (dir) { 3701cb0ef41Sopenharmony_ci dir.closeSync(); 3711cb0ef41Sopenharmony_ci } 3721cb0ef41Sopenharmony_ci } 3731cb0ef41Sopenharmony_ci} 3741cb0ef41Sopenharmony_ci 3751cb0ef41Sopenharmony_cifunction mergeCoverage(merged, coverage) { 3761cb0ef41Sopenharmony_ci for (let i = 0; i < coverage.length; ++i) { 3771cb0ef41Sopenharmony_ci const newScript = coverage[i]; 3781cb0ef41Sopenharmony_ci const { url } = newScript; 3791cb0ef41Sopenharmony_ci 3801cb0ef41Sopenharmony_ci // The first part of this check filters out the node_modules/ directory 3811cb0ef41Sopenharmony_ci // from the results. This filter is applied first because most real world 3821cb0ef41Sopenharmony_ci // applications will be dominated by third party dependencies. The second 3831cb0ef41Sopenharmony_ci // part of the check filters out core modules, which start with 'node:' in 3841cb0ef41Sopenharmony_ci // coverage reports, as well as any invalid coverages which have been 3851cb0ef41Sopenharmony_ci // observed on Windows. 3861cb0ef41Sopenharmony_ci if (StringPrototypeIncludes(url, '/node_modules/') || 3871cb0ef41Sopenharmony_ci !StringPrototypeStartsWith(url, 'file:')) { 3881cb0ef41Sopenharmony_ci continue; 3891cb0ef41Sopenharmony_ci } 3901cb0ef41Sopenharmony_ci 3911cb0ef41Sopenharmony_ci const oldScript = merged.get(url); 3921cb0ef41Sopenharmony_ci 3931cb0ef41Sopenharmony_ci if (oldScript === undefined) { 3941cb0ef41Sopenharmony_ci merged.set(url, newScript); 3951cb0ef41Sopenharmony_ci } else { 3961cb0ef41Sopenharmony_ci mergeCoverageScripts(oldScript, newScript); 3971cb0ef41Sopenharmony_ci } 3981cb0ef41Sopenharmony_ci } 3991cb0ef41Sopenharmony_ci} 4001cb0ef41Sopenharmony_ci 4011cb0ef41Sopenharmony_cifunction mergeCoverageScripts(oldScript, newScript) { 4021cb0ef41Sopenharmony_ci // Merge the functions from the new coverage into the functions from the 4031cb0ef41Sopenharmony_ci // existing (merged) coverage. 4041cb0ef41Sopenharmony_ci for (let i = 0; i < newScript.functions.length; ++i) { 4051cb0ef41Sopenharmony_ci const newFn = newScript.functions[i]; 4061cb0ef41Sopenharmony_ci let found = false; 4071cb0ef41Sopenharmony_ci 4081cb0ef41Sopenharmony_ci for (let j = 0; j < oldScript.functions.length; ++j) { 4091cb0ef41Sopenharmony_ci const oldFn = oldScript.functions[j]; 4101cb0ef41Sopenharmony_ci 4111cb0ef41Sopenharmony_ci if (newFn.functionName === oldFn.functionName && 4121cb0ef41Sopenharmony_ci newFn.ranges?.[0].startOffset === oldFn.ranges?.[0].startOffset && 4131cb0ef41Sopenharmony_ci newFn.ranges?.[0].endOffset === oldFn.ranges?.[0].endOffset) { 4141cb0ef41Sopenharmony_ci // These are the same functions. 4151cb0ef41Sopenharmony_ci found = true; 4161cb0ef41Sopenharmony_ci 4171cb0ef41Sopenharmony_ci // If newFn is block level coverage, then it will: 4181cb0ef41Sopenharmony_ci // - Replace oldFn if oldFn is not block level coverage. 4191cb0ef41Sopenharmony_ci // - Merge with oldFn if it is also block level coverage. 4201cb0ef41Sopenharmony_ci // If newFn is not block level coverage, then it has no new data. 4211cb0ef41Sopenharmony_ci if (newFn.isBlockCoverage) { 4221cb0ef41Sopenharmony_ci if (oldFn.isBlockCoverage) { 4231cb0ef41Sopenharmony_ci // Merge the oldFn ranges with the newFn ranges. 4241cb0ef41Sopenharmony_ci mergeCoverageRanges(oldFn, newFn); 4251cb0ef41Sopenharmony_ci } else { 4261cb0ef41Sopenharmony_ci // Replace oldFn with newFn. 4271cb0ef41Sopenharmony_ci oldFn.isBlockCoverage = true; 4281cb0ef41Sopenharmony_ci oldFn.ranges = newFn.ranges; 4291cb0ef41Sopenharmony_ci } 4301cb0ef41Sopenharmony_ci } 4311cb0ef41Sopenharmony_ci 4321cb0ef41Sopenharmony_ci break; 4331cb0ef41Sopenharmony_ci } 4341cb0ef41Sopenharmony_ci } 4351cb0ef41Sopenharmony_ci 4361cb0ef41Sopenharmony_ci if (!found) { 4371cb0ef41Sopenharmony_ci // This is a new function to track. This is possible because V8 can 4381cb0ef41Sopenharmony_ci // generate a different list of functions depending on which code paths 4391cb0ef41Sopenharmony_ci // are executed. For example, if a code path dynamically creates a 4401cb0ef41Sopenharmony_ci // function, but that code path is not executed then the function does 4411cb0ef41Sopenharmony_ci // not show up in the coverage report. Unfortunately, this also means 4421cb0ef41Sopenharmony_ci // that the function counts in the coverage summary can never be 4431cb0ef41Sopenharmony_ci // guaranteed to be 100% accurate. 4441cb0ef41Sopenharmony_ci ArrayPrototypePush(oldScript.functions, newFn); 4451cb0ef41Sopenharmony_ci } 4461cb0ef41Sopenharmony_ci } 4471cb0ef41Sopenharmony_ci} 4481cb0ef41Sopenharmony_ci 4491cb0ef41Sopenharmony_cifunction mergeCoverageRanges(oldFn, newFn) { 4501cb0ef41Sopenharmony_ci const mergedRanges = new SafeSet(); 4511cb0ef41Sopenharmony_ci 4521cb0ef41Sopenharmony_ci // Keep all of the existing covered ranges. 4531cb0ef41Sopenharmony_ci for (let i = 0; i < oldFn.ranges.length; ++i) { 4541cb0ef41Sopenharmony_ci const oldRange = oldFn.ranges[i]; 4551cb0ef41Sopenharmony_ci 4561cb0ef41Sopenharmony_ci if (oldRange.count > 0) { 4571cb0ef41Sopenharmony_ci mergedRanges.add(oldRange); 4581cb0ef41Sopenharmony_ci } 4591cb0ef41Sopenharmony_ci } 4601cb0ef41Sopenharmony_ci 4611cb0ef41Sopenharmony_ci // Merge in the new ranges where appropriate. 4621cb0ef41Sopenharmony_ci for (let i = 0; i < newFn.ranges.length; ++i) { 4631cb0ef41Sopenharmony_ci const newRange = newFn.ranges[i]; 4641cb0ef41Sopenharmony_ci let exactMatch = false; 4651cb0ef41Sopenharmony_ci 4661cb0ef41Sopenharmony_ci for (let j = 0; j < oldFn.ranges.length; ++j) { 4671cb0ef41Sopenharmony_ci const oldRange = oldFn.ranges[j]; 4681cb0ef41Sopenharmony_ci 4691cb0ef41Sopenharmony_ci if (doesRangeEqualOtherRange(newRange, oldRange)) { 4701cb0ef41Sopenharmony_ci // These are the same ranges, so keep the existing one. 4711cb0ef41Sopenharmony_ci oldRange.count += newRange.count; 4721cb0ef41Sopenharmony_ci mergedRanges.add(oldRange); 4731cb0ef41Sopenharmony_ci exactMatch = true; 4741cb0ef41Sopenharmony_ci break; 4751cb0ef41Sopenharmony_ci } 4761cb0ef41Sopenharmony_ci 4771cb0ef41Sopenharmony_ci // Look at ranges representing missing coverage and add ranges that 4781cb0ef41Sopenharmony_ci // represent the intersection. 4791cb0ef41Sopenharmony_ci if (oldRange.count === 0 && newRange.count === 0) { 4801cb0ef41Sopenharmony_ci if (doesRangeContainOtherRange(oldRange, newRange)) { 4811cb0ef41Sopenharmony_ci // The new range is completely within the old range. Discard the 4821cb0ef41Sopenharmony_ci // larger (old) range, and keep the smaller (new) range. 4831cb0ef41Sopenharmony_ci mergedRanges.add(newRange); 4841cb0ef41Sopenharmony_ci } else if (doesRangeContainOtherRange(newRange, oldRange)) { 4851cb0ef41Sopenharmony_ci // The old range is completely within the new range. Discard the 4861cb0ef41Sopenharmony_ci // larger (new) range, and keep the smaller (old) range. 4871cb0ef41Sopenharmony_ci mergedRanges.add(oldRange); 4881cb0ef41Sopenharmony_ci } 4891cb0ef41Sopenharmony_ci } 4901cb0ef41Sopenharmony_ci } 4911cb0ef41Sopenharmony_ci 4921cb0ef41Sopenharmony_ci // Add new ranges that do not represent missing coverage. 4931cb0ef41Sopenharmony_ci if (newRange.count > 0 && !exactMatch) { 4941cb0ef41Sopenharmony_ci mergedRanges.add(newRange); 4951cb0ef41Sopenharmony_ci } 4961cb0ef41Sopenharmony_ci } 4971cb0ef41Sopenharmony_ci 4981cb0ef41Sopenharmony_ci oldFn.ranges = ArrayFrom(mergedRanges); 4991cb0ef41Sopenharmony_ci} 5001cb0ef41Sopenharmony_ci 5011cb0ef41Sopenharmony_cifunction doesRangeEqualOtherRange(range, otherRange) { 5021cb0ef41Sopenharmony_ci return range.startOffset === otherRange.startOffset && 5031cb0ef41Sopenharmony_ci range.endOffset === otherRange.endOffset; 5041cb0ef41Sopenharmony_ci} 5051cb0ef41Sopenharmony_ci 5061cb0ef41Sopenharmony_cifunction doesRangeContainOtherRange(range, otherRange) { 5071cb0ef41Sopenharmony_ci return range.startOffset <= otherRange.startOffset && 5081cb0ef41Sopenharmony_ci range.endOffset >= otherRange.endOffset; 5091cb0ef41Sopenharmony_ci} 5101cb0ef41Sopenharmony_ci 5111cb0ef41Sopenharmony_cimodule.exports = { setupCoverage, TestCoverage }; 512