| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 7d07c95 commit 4174b73
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -490,6 +490,40 @@ For example, to run a module with "development" resolutions: | |||
| 490 | 490 | node -C development app.js | |
| 491 | 491 | ``` | |
| 492 | 492 | ||
| 493 | + ### `--test-coverage-exclude` | ||
| 494 | + | ||
| 495 | + <!-- YAML | ||
| 496 | + added: | ||
| 497 | + - REPLACEME | ||
| 498 | + --> | ||
| 499 | + | ||
| 500 | + > Stability: 1 - Experimental | ||
| 501 | + | ||
| 502 | + Excludes specific files from code coverage using a glob pattern, which can match | ||
| 503 | + both absolute and relative file paths. | ||
| 504 | + | ||
| 505 | + This option may be specified multiple times to exclude multiple glob patterns. | ||
| 506 | + | ||
| 507 | + If both `--test-coverage-exclude` and `--test-coverage-include` are provided, | ||
| 508 | + files must meet **both** criteria to be included in the coverage report. | ||
| 509 | + | ||
| 510 | + ### `--test-coverage-include` | ||
| 511 | + | ||
| 512 | + <!-- YAML | ||
| 513 | + added: | ||
| 514 | + - REPLACEME | ||
| 515 | + --> | ||
| 516 | + | ||
| 517 | + > Stability: 1 - Experimental | ||
| 518 | + | ||
| 519 | + Includes specific files in code coverage using a glob pattern, which can match | ||
| 520 | + both absolute and relative file paths. | ||
| 521 | + | ||
| 522 | + This option may be specified multiple times to include multiple glob patterns. | ||
| 523 | + | ||
| 524 | + If both `--test-coverage-exclude` and `--test-coverage-include` are provided, | ||
| 525 | + files must meet **both** criteria to be included in the coverage report. | ||
| 526 | + | ||
| 493 | 527 | ### `--cpu-prof` | |
| 494 | 528 | ||
| 495 | 529 | <!-- YAML | |
@@ -2917,6 +2951,8 @@ one is included in the list below. | |||
| 2917 | 2951 | * `--secure-heap-min` | |
| 2918 | 2952 | * `--secure-heap` | |
| 2919 | 2953 | * `--snapshot-blob` | |
| 2954 | + * `--test-coverage-exclude` | ||
| 2955 | + * `--test-coverage-include` | ||
| 2920 | 2956 | * `--test-only` | |
| 2921 | 2957 | * `--test-reporter-destination` | |
| 2922 | 2958 | * `--test-reporter` | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -511,11 +511,6 @@ node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-de | |||
| 511 | 511 | * No test results are reported by this reporter. | |
| 512 | 512 | * This reporter should ideally be used alongside another reporter. | |
| 513 | 513 | ||
| 514 | - ### Limitations | ||
| 515 | - | ||
| 516 | - The test runner's code coverage functionality does not support excluding | ||
| 517 | - specific files or directories from the coverage report. | ||
| 518 | - | ||
| 519 | 514 | ## Mocking | |
| 520 | 515 | ||
| 521 | 516 | The `node:test` module supports mocking during testing via a top-level `mock` | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -435,6 +435,12 @@ Starts the Node.js command line test runner. | |||
| 435 | 435 | The maximum number of test files that the test runner CLI will execute | |
| 436 | 436 | concurrently. | |
| 437 | 437 | . | |
| 438 | + .It Fl -test-coverage-exclude | ||
| 439 | + A glob pattern that excludes matching files from the coverage report | ||
| 440 | + . | ||
| 441 | + .It Fl -test-coverage-include | ||
| 442 | + A glob pattern that only includes matching files in the coverage report | ||
| 443 | + . | ||
| 438 | 444 | .It Fl -test-force-exit | |
| 439 | 445 | Configures the test runner to exit the process once all known tests have | |
| 440 | 446 | finished executing even if the event loop would otherwise remain active. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,15 +25,18 @@ const { | |||
| 25 | 25 | readFileSync, | |
| 26 | 26 | } = require('fs'); | |
| 27 | 27 | const { setupCoverageHooks } = require('internal/util'); | |
| 28 | + const { getOptionValue } = require('internal/options'); | ||
| 28 | 29 | const { tmpdir } = require('os'); | |
| 29 | - const { join, resolve } = require('path'); | ||
| 30 | + const { join, resolve, relative, matchesGlob } = require('path'); | ||
| 30 | 31 | const { fileURLToPath } = require('internal/url'); | |
| 31 | 32 | const { kMappings, SourceMap } = require('internal/source_map/source_map'); | |
| 32 | 33 | const kCoverageFileRegex = /^coverage-(\d+)-(\d{13})-(\d+)\.json$/; | |
| 33 | 34 | const kIgnoreRegex = /\/\* node:coverage ignore next (?<count>\d+ )?\*\//; | |
| 34 | 35 | const kLineEndingRegex = /\r?\n$/u; | |
| 35 | 36 | const kLineSplitRegex = /(?<=\r?\n)/u; | |
| 36 | 37 | const kStatusRegex = /\/\* node:coverage (?<status>enable|disable) \*\//; | |
| 38 | + const excludeFileGlobs = getOptionValue('--test-coverage-exclude'); | ||
| 39 | + const includeFileGlobs = getOptionValue('--test-coverage-include'); | ||
| 37 | 40 | ||
| 38 | 41 | class CoverageLine { | |
| 39 | 42 | constructor(line, startOffset, src, length = src?.length) { | |
@@ -308,7 +311,7 @@ class TestCoverage { | |||
| 308 | 311 | ||
| 309 | 312 | const coverageFile = join(this.coverageDirectory, entry.name); | |
| 310 | 313 | const coverage = JSONParse(readFileSync(coverageFile, 'utf8')); | |
| 311 | - mergeCoverage(result, this.mapCoverageWithSourceMap(coverage)); | ||
| 314 | + mergeCoverage(result, this.mapCoverageWithSourceMap(coverage), this.workingDirectory); | ||
| 312 | 315 | } | |
| 313 | 316 | ||
| 314 | 317 | return ArrayFrom(result.values()); | |
@@ -331,7 +334,7 @@ class TestCoverage { | |||
| 331 | 334 | const script = result[i]; | |
| 332 | 335 | const { url, functions } = script; | |
| 333 | 336 | ||
| 334 | - if (shouldSkipFileCoverage(url) || sourceMapCache[url] == null) { | ||
| 337 | + if (shouldSkipFileCoverage(url, this.workingDirectory) || sourceMapCache[url] == null) { | ||
| 335 | 338 | newResult.set(url, script); | |
| 336 | 339 | continue; | |
| 337 | 340 | } | |
@@ -485,22 +488,42 @@ function mapRangeToLines(range, lines) { | |||
| 485 | 488 | return { __proto__: null, lines: mappedLines, ignoredLines }; | |
| 486 | 489 | } | |
| 487 | 490 | ||
| 488 | - function shouldSkipFileCoverage(url) { | ||
| 489 | - // The first part of this check filters out the node_modules/ directory | ||
| 490 | - // from the results. This filter is applied first because most real world | ||
| 491 | - // applications will be dominated by third party dependencies. The second | ||
| 492 | - // part of the check filters out core modules, which start with 'node:' in | ||
| 491 | + function shouldSkipFileCoverage(url, workingDirectory) { | ||
| 492 | + // This check filters out core modules, which start with 'node:' in | ||
| 493 | 493 | // coverage reports, as well as any invalid coverages which have been | |
| 494 | 494 | // observed on Windows. | |
| 495 | - return StringPrototypeIncludes(url, '/node_modules/') || !StringPrototypeStartsWith(url, 'file:'); | ||
| 495 | + if (!StringPrototypeStartsWith(url, 'file:')) return true; | ||
| 496 | + | ||
| 497 | + const absolutePath = fileURLToPath(url); | ||
| 498 | + const relativePath = relative(workingDirectory, absolutePath); | ||
| 499 | + | ||
| 500 | + // This check filters out files that match the exclude globs. | ||
| 501 | + if (excludeFileGlobs?.length > 0) { | ||
| 502 | + for (let i = 0; i < excludeFileGlobs.length; ++i) { | ||
| 503 | + if (matchesGlob(relativePath, excludeFileGlobs[i]) || | ||
| 504 | + matchesGlob(absolutePath, excludeFileGlobs[i])) return true; | ||
| 505 | + } | ||
| 506 | + } | ||
| 507 | + | ||
| 508 | + // This check filters out files that do not match the include globs. | ||
| 509 | + if (includeFileGlobs?.length > 0) { | ||
| 510 | + for (let i = 0; i < includeFileGlobs.length; ++i) { | ||
| 511 | + if (matchesGlob(relativePath, includeFileGlobs[i]) || | ||
| 512 | + matchesGlob(absolutePath, includeFileGlobs[i])) return false; | ||
| 513 | + } | ||
| 514 | + return true; | ||
| 515 | + } | ||
| 516 | + | ||
| 517 | + // This check filters out the node_modules/ directory, unless it is explicitly included. | ||
| 518 | + return StringPrototypeIncludes(url, '/node_modules/'); | ||
| 496 | 519 | } | |
| 497 | 520 | ||
| 498 | - function mergeCoverage(merged, coverage) { | ||
| 521 | + function mergeCoverage(merged, coverage, workingDirectory) { | ||
| 499 | 522 | for (let i = 0; i < coverage.length; ++i) { | |
| 500 | 523 | const newScript = coverage[i]; | |
| 501 | 524 | const { url } = newScript; | |
| 502 | 525 | ||
| 503 | - if (shouldSkipFileCoverage(url)) { | ||
| 526 | + if (shouldSkipFileCoverage(url, workingDirectory)) { | ||
| 504 | 527 | continue; | |
| 505 | 528 | } | |
| 506 | 529 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -674,6 +674,14 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { | |||
| 674 | 674 | AddOption("--test-skip-pattern", | |
| 675 | 675 | "run tests whose name do not match this regular expression", | |
| 676 | 676 | &EnvironmentOptions::test_skip_pattern); | |
| 677 | + AddOption("--test-coverage-include", | ||
| 678 | + "include files in coverage report that match this glob pattern", | ||
| 679 | + &EnvironmentOptions::coverage_include_pattern, | ||
| 680 | + kAllowedInEnvvar); | ||
| 681 | + AddOption("--test-coverage-exclude", | ||
| 682 | + "exclude files from coverage report that match this glob pattern", | ||
| 683 | + &EnvironmentOptions::coverage_exclude_pattern, | ||
| 684 | + kAllowedInEnvvar); | ||
| 677 | 685 | AddOption("--test-udp-no-try-send", "", // For testing only. | |
| 678 | 686 | &EnvironmentOptions::test_udp_no_try_send); | |
| 679 | 687 | AddOption("--throw-deprecation", | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -187,6 +187,8 @@ class EnvironmentOptions : public Options { | |||
| 187 | 187 | bool test_udp_no_try_send = false; | |
| 188 | 188 | std::string test_shard; | |
| 189 | 189 | std::vector<std::string> test_skip_pattern; | |
| 190 | + std::vector<std::string> coverage_include_pattern; | ||
| 191 | + std::vector<std::string> coverage_exclude_pattern; | ||
| 190 | 192 | bool throw_deprecation = false; | |
| 191 | 193 | bool trace_deprecation = false; | |
| 192 | 194 | bool trace_exit = false; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -335,3 +335,96 @@ test('coverage with ESM hook - source transpiled', skipIfNoInspector, () => { | |||
| 335 | 335 | assert(result.stdout.toString().includes(report)); | |
| 336 | 336 | assert.strictEqual(result.status, 0); | |
| 337 | 337 | }); | |
| 338 | + | ||
| 339 | + test('coverage with excluded files', skipIfNoInspector, () => { | ||
| 340 | + const fixture = fixtures.path('test-runner', 'coverage.js'); | ||
| 341 | + const args = [ | ||
| 342 | + '--experimental-test-coverage', '--test-reporter', 'tap', | ||
| 343 | + '--test-coverage-exclude=test/*/test-runner/invalid-tap.js', | ||
| 344 | + fixture]; | ||
| 345 | + const result = spawnSync(process.execPath, args); | ||
| 346 | + const report = [ | ||
| 347 | + '# start of coverage report', | ||
| 348 | + '# ' + '-'.repeat(112), | ||
| 349 | + '# file | line % | branch % | funcs % | uncovered lines', | ||
| 350 | + '# ' + '-'.repeat(112), | ||
| 351 | + '# test/fixtures/test-runner/coverage.js | 78.65 | 38.46 | 60.00 | 12-13 16-22 27 39 43-44 61-62 66-67 71-72', | ||
| 352 | + '# test/fixtures/v8-coverage/throw.js | 71.43 | 50.00 | 100.00 | 5-6', | ||
| 353 | + '# ' + '-'.repeat(112), | ||
| 354 | + '# all files | 78.13 | 40.00 | 60.00 |', | ||
| 355 | + '# ' + '-'.repeat(112), | ||
| 356 | + '# end of coverage report', | ||
| 357 | + ].join('\n'); | ||
| 358 | + | ||
| 359 | + | ||
| 360 | + if (common.isWindows) { | ||
| 361 | + return report.replaceAll('/', '\\'); | ||
| 362 | + } | ||
| 363 | + | ||
| 364 | + assert(result.stdout.toString().includes(report)); | ||
| 365 | + assert.strictEqual(result.status, 0); | ||
| 366 | + assert(!findCoverageFileForPid(result.pid)); | ||
| 367 | + }); | ||
| 368 | + | ||
| 369 | + test('coverage with included files', skipIfNoInspector, () => { | ||
| 370 | + const fixture = fixtures.path('test-runner', 'coverage.js'); | ||
| 371 | + const args = [ | ||
| 372 | + '--experimental-test-coverage', '--test-reporter', 'tap', | ||
| 373 | + '--test-coverage-include=test/fixtures/test-runner/coverage.js', | ||
| 374 | + '--test-coverage-include=test/fixtures/v8-coverage/throw.js', | ||
| 375 | + fixture, | ||
| 376 | + ]; | ||
| 377 | + const result = spawnSync(process.execPath, args); | ||
| 378 | + const report = [ | ||
| 379 | + '# start of coverage report', | ||
| 380 | + '# ' + '-'.repeat(112), | ||
| 381 | + '# file | line % | branch % | funcs % | uncovered lines', | ||
| 382 | + '# ' + '-'.repeat(112), | ||
| 383 | + '# test/fixtures/test-runner/coverage.js | 78.65 | 38.46 | 60.00 | 12-13 16-22 27 39 43-44 61-62 66-67 71-72', | ||
| 384 | + '# test/fixtures/v8-coverage/throw.js | 71.43 | 50.00 | 100.00 | 5-6', | ||
| 385 | + '# ' + '-'.repeat(112), | ||
| 386 | + '# all files | 78.13 | 40.00 | 60.00 |', | ||
| 387 | + '# ' + '-'.repeat(112), | ||
| 388 | + '# end of coverage report', | ||
| 389 | + ].join('\n'); | ||
| 390 | + | ||
| 391 | + | ||
| 392 | + if (common.isWindows) { | ||
| 393 | + return report.replaceAll('/', '\\'); | ||
| 394 | + } | ||
| 395 | + | ||
| 396 | + assert(result.stdout.toString().includes(report)); | ||
| 397 | + assert.strictEqual(result.status, 0); | ||
| 398 | + assert(!findCoverageFileForPid(result.pid)); | ||
| 399 | + }); | ||
| 400 | + | ||
| 401 | + test('coverage with included and excluded files', skipIfNoInspector, () => { | ||
| 402 | + const fixture = fixtures.path('test-runner', 'coverage.js'); | ||
| 403 | + const args = [ | ||
| 404 | + '--experimental-test-coverage', '--test-reporter', 'tap', | ||
| 405 | + '--test-coverage-include=test/fixtures/test-runner/*.js', | ||
| 406 | + '--test-coverage-exclude=test/fixtures/test-runner/*-tap.js', | ||
| 407 | + fixture, | ||
| 408 | + ]; | ||
| 409 | + const result = spawnSync(process.execPath, args); | ||
| 410 | + const report = [ | ||
| 411 | + '# start of coverage report', | ||
| 412 | + '# ' + '-'.repeat(112), | ||
| 413 | + '# file | line % | branch % | funcs % | uncovered lines', | ||
| 414 | + '# ' + '-'.repeat(112), | ||
| 415 | + '# test/fixtures/test-runner/coverage.js | 78.65 | 38.46 | 60.00 | 12-13 16-22 27 39 43-44 61-62 66-67 71-72', | ||
| 416 | + '# ' + '-'.repeat(112), | ||
| 417 | + '# all files | 78.65 | 38.46 | 60.00 |', | ||
| 418 | + '# ' + '-'.repeat(112), | ||
| 419 | + '# end of coverage report', | ||
| 420 | + ].join('\n'); | ||
| 421 | + | ||
| 422 | + | ||
| 423 | + if (common.isWindows) { | ||
| 424 | + return report.replaceAll('/', '\\'); | ||
| 425 | + } | ||
| 426 | + | ||
| 427 | + assert(result.stdout.toString().includes(report)); | ||
| 428 | + assert.strictEqual(result.status, 0); | ||
| 429 | + assert(!findCoverageFileForPid(result.pid)); | ||
| 430 | + }); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments