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