diff --git a/lib/internal/test_runner/coverage.js b/lib/internal/test_runner/coverage.js index 1ed45028f61575..68ee42c3746353 100644 --- a/lib/internal/test_runner/coverage.js +++ b/lib/internal/test_runner/coverage.js @@ -338,7 +338,7 @@ function mapRangeToLines(range, lines) { mid = MathFloor((start + end) / 2); let line = lines[mid]; - if (startOffset >= line.startOffset && startOffset <= line.endOffset) { + if (startOffset >= line?.startOffset && startOffset <= line?.endOffset) { while (endOffset > line?.startOffset) { // If the range is not covered, and the range covers the entire line, // then mark that line as not covered. @@ -363,7 +363,7 @@ function mapRangeToLines(range, lines) { } break; - } else if (startOffset >= line.endOffset) { + } else if (startOffset >= line?.endOffset) { start = mid + 1; } else { end = mid - 1; @@ -538,4 +538,4 @@ function doesRangeContainOtherRange(range, otherRange) { range.endOffset >= otherRange.endOffset; } -module.exports = { setupCoverage }; +module.exports = { setupCoverage, TestCoverage }; diff --git a/lib/internal/test_runner/test.js b/lib/internal/test_runner/test.js index 975ad4ac08b41f..4afb93f4a60df0 100644 --- a/lib/internal/test_runner/test.js +++ b/lib/internal/test_runner/test.js @@ -737,6 +737,8 @@ class Test extends AsyncResource { this.reported = true; reporter.plan(nesting, loc, harness.counters.topLevel); + // Call this harness.coverage() before collecting diagnostics, since failure to collect coverage is a diagnostic. + const coverage = harness.coverage(); for (let i = 0; i < diagnostics.length; i++) { reporter.diagnostic(nesting, loc, diagnostics[i]); } @@ -750,8 +752,6 @@ class Test extends AsyncResource { reporter.diagnostic(nesting, loc, `todo ${harness.counters.todo}`); reporter.diagnostic(nesting, loc, `duration_ms ${this.duration()}`); - const coverage = harness.coverage(); - if (coverage) { reporter.coverage(nesting, loc, coverage); } diff --git a/test/fixtures/test-runner/output/coverage_failure.js b/test/fixtures/test-runner/output/coverage_failure.js new file mode 100644 index 00000000000000..6c4d25ce081cad --- /dev/null +++ b/test/fixtures/test-runner/output/coverage_failure.js @@ -0,0 +1,13 @@ +// Flags: --expose-internals --experimental-test-coverage + +'use strict'; +require('../../../common'); +const { TestCoverage } = require('internal/test_runner/coverage'); +const { test, mock } = require('node:test'); + +mock.method(TestCoverage.prototype, 'summary', () => { + throw new Error('Failed to collect coverage'); +}); + +test('ok'); + diff --git a/test/fixtures/test-runner/output/coverage_failure.snapshot b/test/fixtures/test-runner/output/coverage_failure.snapshot new file mode 100644 index 00000000000000..62f39ebede943a --- /dev/null +++ b/test/fixtures/test-runner/output/coverage_failure.snapshot @@ -0,0 +1,16 @@ +TAP version 13 +# Subtest: ok +ok 1 - ok + --- + duration_ms: * + ... +1..1 +# Warning: Could not report code coverage. Error: Failed to collect coverage +# tests 1 +# suites 0 +# pass 1 +# fail 0 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms * diff --git a/test/parallel/test-runner-output.mjs b/test/parallel/test-runner-output.mjs index 8db41bff38a114..a45ac62d5f0eb7 100644 --- a/test/parallel/test-runner-output.mjs +++ b/test/parallel/test-runner-output.mjs @@ -87,6 +87,7 @@ const tests = [ replaceTestDuration, ), }, + process.features.inspector ? { name: 'test-runner/output/coverage_failure.js' } : false, ] .filter(Boolean) .map(({ name, tty, transform }) => ({