| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1187,6 +1187,16 @@ Starts the Node.js command line test runner. This flag cannot be combined with | |||
| 1187 | 1187 | `--check`, `--eval`, `--interactive`, or the inspector. See the documentation | |
| 1188 | 1188 | on [running tests from the command line][] for more details. | |
| 1189 | 1189 | ||
| 1190 | + ### `--test-name-pattern` | ||
| 1191 | + | ||
| 1192 | + <!-- YAML | ||
| 1193 | + added: REPLACEME | ||
| 1194 | + --> | ||
| 1195 | + | ||
| 1196 | + A regular expression that configures the test runner to only execute tests | ||
| 1197 | + whose name matches the provided pattern. See the documentation on | ||
| 1198 | + [filtering tests by name][] for more details. | ||
| 1199 | + | ||
| 1190 | 1200 | ### `--test-only` | |
| 1191 | 1201 | ||
| 1192 | 1202 | <!-- YAML | |
@@ -2262,6 +2272,7 @@ done | |||
| 2262 | 2272 | [debugger]: debugger.md | |
| 2263 | 2273 | [debugging security implications]: https://nodejs.org/en/docs/guides/debugging-getting-started/#security-implications | |
| 2264 | 2274 | [emit_warning]: process.md#processemitwarningwarning-options | |
| 2275 | + [filtering tests by name]: test.md#filtering-tests-by-name | ||
| 2265 | 2276 | [jitless]: https://v8.dev/blog/jitless | |
| 2266 | 2277 | [libuv threadpool documentation]: https://docs.libuv.org/en/latest/threadpool.html | |
| 2267 | 2278 | [remote code execution]: https://www.owasp.org/index.php/Code_Injection | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -220,6 +220,42 @@ test('this test is not run', () => { | |||
| 220 | 220 | }); | |
| 221 | 221 | ``` | |
| 222 | 222 | ||
| 223 | + ## Filtering tests by name | ||
| 224 | + | ||
| 225 | + The [`--test-name-pattern`][] command-line option can be used to only run tests | ||
| 226 | + whose name matches the provided pattern. Test name patterns are interpreted as | ||
| 227 | + JavaScript regular expressions. The `--test-name-pattern` option can be | ||
| 228 | + specified multiple times in order to run nested tests. For each test that is | ||
| 229 | + executed, any corresponding test hooks, such as `beforeEach()`, are also | ||
| 230 | + run. | ||
| 231 | + | ||
| 232 | + Given the following test file, starting Node.js with the | ||
| 233 | + `--test-name-pattern="test [1-3]"` option would cause the test runner to execute | ||
| 234 | + `test 1`, `test 2`, and `test 3`. If `test 1` did not match the test name | ||
| 235 | + pattern, then its subtests would not execute, despite matching the pattern. The | ||
| 236 | + same set of tests could also be executed by passing `--test-name-pattern` | ||
| 237 | + multiple times (e.g. `--test-name-pattern="test 1"`, | ||
| 238 | + `--test-name-pattern="test 2"`, etc.). | ||
| 239 | + | ||
| 240 | + ```js | ||
| 241 | + test('test 1', async (t) => { | ||
| 242 | + await t.test('test 2'); | ||
| 243 | + await t.test('test 3'); | ||
| 244 | + }); | ||
| 245 | + | ||
| 246 | + test('Test 4', async (t) => { | ||
| 247 | + await t.test('Test 5'); | ||
| 248 | + await t.test('test 6'); | ||
| 249 | + }); | ||
| 250 | + ``` | ||
| 251 | + | ||
| 252 | + Test name patterns can also be specified using regular expression literals. This | ||
| 253 | + allows regular expression flags to be used. In the previous example, starting | ||
| 254 | + Node.js with `--test-name-pattern="/test [4-5]/i"` would match `Test 4` and | ||
| 255 | + `Test 5` because the pattern is case-insensitive. | ||
| 256 | + | ||
| 257 | + Test name patterns do not change the set of files that the test runner executes. | ||
| 258 | + | ||
| 223 | 259 | ## Extraneous asynchronous activity | |
| 224 | 260 | ||
| 225 | 261 | Once a test function finishes executing, the TAP results are output as quickly | |
@@ -896,6 +932,7 @@ added: v18.7.0 | |||
| 896 | 932 | aborted. | |
| 897 | 933 | ||
| 898 | 934 | [TAP]: https://testanything.org/ | |
| 935 | + [`--test-name-pattern`]: cli.md#--test-name-pattern | ||
| 899 | 936 | [`--test-only`]: cli.md#--test-only | |
| 900 | 937 | [`--test`]: cli.md#--test | |
| 901 | 938 | [`SuiteContext`]: #class-suitecontext | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -387,6 +387,10 @@ Specify the minimum allocation from the OpenSSL secure heap. The default is 2. T | |||
| 387 | 387 | .It Fl -test | |
| 388 | 388 | Starts the Node.js command line test runner. | |
| 389 | 389 | . | |
| 390 | + .It Fl -test-name-pattern | ||
| 391 | + A regular expression that configures the test runner to only execute tests | ||
| 392 | + whose name matches the provided pattern. | ||
| 393 | + . | ||
| 390 | 394 | .It Fl -test-only | |
| 391 | 395 | Configures the test runner to only execute top level tests that have the `only` | |
| 392 | 396 | option set. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,9 +1,11 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | 2 | const { | |
| 3 | + ArrayPrototypeMap, | ||
| 3 | 4 | ArrayPrototypePush, | |
| 4 | 5 | ArrayPrototypeReduce, | |
| 5 | 6 | ArrayPrototypeShift, | |
| 6 | 7 | ArrayPrototypeSlice, | |
| 8 | + ArrayPrototypeSome, | ||
| 7 | 9 | ArrayPrototypeUnshift, | |
| 8 | 10 | FunctionPrototype, | |
| 9 | 11 | MathMax, | |
@@ -12,6 +14,7 @@ const { | |||
| 12 | 14 | PromisePrototypeThen, | |
| 13 | 15 | PromiseResolve, | |
| 14 | 16 | ReflectApply, | |
| 17 | + RegExpPrototypeExec, | ||
| 15 | 18 | SafeMap, | |
| 16 | 19 | SafeSet, | |
| 17 | 20 | SafePromiseAll, | |
@@ -30,7 +33,11 @@ const { | |||
| 30 | 33 | } = require('internal/errors'); | |
| 31 | 34 | const { getOptionValue } = require('internal/options'); | |
| 32 | 35 | const { TapStream } = require('internal/test_runner/tap_stream'); | |
| 33 | - const { createDeferredCallback, isTestFailureError } = require('internal/test_runner/utils'); | ||
| 36 | + const { | ||
| 37 | + convertStringToRegExp, | ||
| 38 | + createDeferredCallback, | ||
| 39 | + isTestFailureError, | ||
| 40 | + } = require('internal/test_runner/utils'); | ||
| 34 | 41 | const { | |
| 35 | 42 | createDeferredPromise, | |
| 36 | 43 | kEmptyObject, | |
@@ -58,6 +65,13 @@ const kDefaultTimeout = null; | |||
| 58 | 65 | const noop = FunctionPrototype; | |
| 59 | 66 | const isTestRunner = getOptionValue('--test'); | |
| 60 | 67 | const testOnlyFlag = !isTestRunner && getOptionValue('--test-only'); | |
| 68 | + const testNamePatternFlag = isTestRunner ? null : | ||
| 69 | + getOptionValue('--test-name-pattern'); | ||
| 70 | + const testNamePatterns = testNamePatternFlag?.length > 0 ? | ||
| 71 | + ArrayPrototypeMap( | ||
| 72 | + testNamePatternFlag, | ||
| 73 | + (re) => convertStringToRegExp(re, '--test-name-pattern') | ||
| 74 | + ) : null; | ||
| 61 | 75 | const kShouldAbort = Symbol('kShouldAbort'); | |
| 62 | 76 | const kRunHook = Symbol('kRunHook'); | |
| 63 | 77 | const kHookNames = ObjectSeal(['before', 'after', 'beforeEach', 'afterEach']); | |
@@ -195,6 +209,18 @@ class Test extends AsyncResource { | |||
| 195 | 209 | this.timeout = timeout; | |
| 196 | 210 | } | |
| 197 | 211 | ||
| 212 | + if (testNamePatterns !== null) { | ||
| 213 | + // eslint-disable-next-line no-use-before-define | ||
| 214 | + const match = this instanceof TestHook || ArrayPrototypeSome( | ||
| 215 | + testNamePatterns, | ||
| 216 | + (re) => RegExpPrototypeExec(re, name) !== null | ||
| 217 | + ); | ||
| 218 | + | ||
| 219 | + if (!match) { | ||
| 220 | + skip = 'test name does not match pattern'; | ||
| 221 | + } | ||
| 222 | + } | ||
| 223 | + | ||
| 198 | 224 | if (testOnlyFlag && !this.only) { | |
| 199 | 225 | skip = '\'only\' option not set'; | |
| 200 | 226 | } | |
@@ -210,7 +236,6 @@ class Test extends AsyncResource { | |||
| 210 | 236 | validateAbortSignal(signal, 'options.signal'); | |
| 211 | 237 | this.#outerSignal?.addEventListener('abort', this.#abortHandler); | |
| 212 | 238 | ||
| 213 | - | ||
| 214 | 239 | this.fn = fn; | |
| 215 | 240 | this.name = name; | |
| 216 | 241 | this.parent = parent; | |
@@ -669,6 +694,7 @@ class ItTest extends Test { | |||
| 669 | 694 | return { ctx: { signal: this.signal, name: this.name }, args: [] }; | |
| 670 | 695 | } | |
| 671 | 696 | } | |
| 697 | + | ||
| 672 | 698 | class Suite extends Test { | |
| 673 | 699 | constructor(options) { | |
| 674 | 700 | super(options); | |
@@ -704,7 +730,6 @@ class Suite extends Test { | |||
| 704 | 730 | return; | |
| 705 | 731 | } | |
| 706 | 732 | ||
| 707 | - | ||
| 708 | 733 | const hookArgs = this.getRunArgs(); | |
| 709 | 734 | await this[kRunHook]('before', hookArgs); | |
| 710 | 735 | const stopPromise = stopTest(this.timeout, this.signal); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,15 +1,17 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | - const { RegExpPrototypeExec } = primordials; | ||
| 2 | + const { RegExp, RegExpPrototypeExec } = primordials; | ||
| 3 | 3 | const { basename } = require('path'); | |
| 4 | 4 | const { createDeferredPromise } = require('internal/util'); | |
| 5 | 5 | const { | |
| 6 | 6 | codes: { | |
| 7 | + ERR_INVALID_ARG_VALUE, | ||
| 7 | 8 | ERR_TEST_FAILURE, | |
| 8 | 9 | }, | |
| 9 | 10 | kIsNodeError, | |
| 10 | 11 | } = require('internal/errors'); | |
| 11 | 12 | ||
| 12 | 13 | const kMultipleCallbackInvocations = 'multipleCallbackInvocations'; | |
| 14 | + const kRegExpPattern = /^\/(.*)\/([a-z]*)$/; | ||
| 13 | 15 | const kSupportedFileExtensions = /\.[cm]?js$/; | |
| 14 | 16 | const kTestFilePattern = /((^test(-.+)?)|(.+[.\-_]test))\.[cm]?js$/; | |
| 15 | 17 | ||
@@ -54,7 +56,26 @@ function isTestFailureError(err) { | |||
| 54 | 56 | return err?.code === 'ERR_TEST_FAILURE' && kIsNodeError in err; | |
| 55 | 57 | } | |
| 56 | 58 | ||
| 59 | + function convertStringToRegExp(str, name) { | ||
| 60 | + const match = RegExpPrototypeExec(kRegExpPattern, str); | ||
| 61 | + const pattern = match?.[1] ?? str; | ||
| 62 | + const flags = match?.[2] || ''; | ||
| 63 | + | ||
| 64 | + try { | ||
| 65 | + return new RegExp(pattern, flags); | ||
| 66 | + } catch (err) { | ||
| 67 | + const msg = err?.message; | ||
| 68 | + | ||
| 69 | + throw new ERR_INVALID_ARG_VALUE( | ||
| 70 | + name, | ||
| 71 | + str, | ||
| 72 | + `is an invalid regular expression.${msg ? ` ${msg}` : ''}` | ||
| 73 | + ); | ||
| 74 | + } | ||
| 75 | + } | ||
| 76 | + | ||
| 57 | 77 | module.exports = { | |
| 78 | + convertStringToRegExp, | ||
| 58 | 79 | createDeferredCallback, | |
| 59 | 80 | doesPathMatchFilter, | |
| 60 | 81 | isSupportedFileType, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -553,6 +553,9 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { | |||
| 553 | 553 | AddOption("--test", | |
| 554 | 554 | "launch test runner on startup", | |
| 555 | 555 | &EnvironmentOptions::test_runner); | |
| 556 | + AddOption("--test-name-pattern", | ||
| 557 | + "run tests whose name matches this regular expression", | ||
| 558 | + &EnvironmentOptions::test_name_pattern); | ||
| 556 | 559 | AddOption("--test-only", | |
| 557 | 560 | "run tests with 'only' option set", | |
| 558 | 561 | &EnvironmentOptions::test_only, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -153,6 +153,7 @@ class EnvironmentOptions : public Options { | |||
| 153 | 153 | std::string redirect_warnings; | |
| 154 | 154 | std::string diagnostic_dir; | |
| 155 | 155 | bool test_runner = false; | |
| 156 | + std::vector<std::string> test_name_pattern; | ||
| 156 | 157 | bool test_only = false; | |
| 157 | 158 | bool test_udp_no_try_send = false; | |
| 158 | 159 | bool throw_deprecation = false; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,47 @@ | |||
| 1 | + // Flags: --no-warnings --test-name-pattern=enabled --test-name-pattern=/pattern/i | ||
| 2 | + 'use strict'; | ||
| 3 | + const common = require('../common'); | ||
| 4 | + const { | ||
| 5 | + after, | ||
| 6 | + afterEach, | ||
| 7 | + before, | ||
| 8 | + beforeEach, | ||
| 9 | + describe, | ||
| 10 | + it, | ||
| 11 | + test, | ||
| 12 | + } = require('node:test'); | ||
| 13 | + | ||
| 14 | + test('top level test disabled', common.mustNotCall()); | ||
| 15 | + test('top level skipped test disabled', { skip: true }, common.mustNotCall()); | ||
| 16 | + test('top level skipped test enabled', { skip: true }, common.mustNotCall()); | ||
| 17 | + it('top level it enabled', common.mustCall()); | ||
| 18 | + it('top level it disabled', common.mustNotCall()); | ||
| 19 | + it.skip('top level skipped it disabled', common.mustNotCall()); | ||
| 20 | + it.skip('top level skipped it enabled', common.mustNotCall()); | ||
| 21 | + describe('top level describe disabled', common.mustNotCall()); | ||
| 22 | + describe.skip('top level skipped describe disabled', common.mustNotCall()); | ||
| 23 | + describe.skip('top level skipped describe enabled', common.mustNotCall()); | ||
| 24 | + test('top level runs because name includes PaTtErN', common.mustCall()); | ||
| 25 | + | ||
| 26 | + test('top level test enabled', common.mustCall(async (t) => { | ||
| 27 | + t.beforeEach(common.mustCall()); | ||
| 28 | + t.afterEach(common.mustCall()); | ||
| 29 | + await t.test( | ||
| 30 | + 'nested test runs because name includes PATTERN', | ||
| 31 | + common.mustCall() | ||
| 32 | + ); | ||
| 33 | + })); | ||
| 34 | + | ||
| 35 | + describe('top level describe enabled', () => { | ||
| 36 | + before(common.mustCall()); | ||
| 37 | + beforeEach(common.mustCall(2)); | ||
| 38 | + afterEach(common.mustCall(2)); | ||
| 39 | + after(common.mustCall()); | ||
| 40 | + | ||
| 41 | + it('nested it disabled', common.mustNotCall()); | ||
| 42 | + it('nested it enabled', common.mustCall()); | ||
| 43 | + describe('nested describe disabled', common.mustNotCall()); | ||
| 44 | + describe('nested describe enabled', common.mustCall(() => { | ||
| 45 | + it('is enabled', common.mustCall()); | ||
| 46 | + })); | ||
| 47 | + }); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,107 @@ | |||
| 1 | + TAP version 13 | ||
| 2 | + # Subtest: top level test disabled | ||
| 3 | + ok 1 - top level test disabled # SKIP test name does not match pattern | ||
| 4 | + --- | ||
| 5 | + duration_ms: * | ||
| 6 | + ... | ||
| 7 | + # Subtest: top level skipped test disabled | ||
| 8 | + ok 2 - top level skipped test disabled # SKIP test name does not match pattern | ||
| 9 | + --- | ||
| 10 | + duration_ms: * | ||
| 11 | + ... | ||
| 12 | + # Subtest: top level skipped test enabled | ||
| 13 | + ok 3 - top level skipped test enabled # SKIP | ||
| 14 | + --- | ||
| 15 | + duration_ms: * | ||
| 16 | + ... | ||
| 17 | + # Subtest: top level it enabled | ||
| 18 | + ok 4 - top level it enabled | ||
| 19 | + --- | ||
| 20 | + duration_ms: * | ||
| 21 | + ... | ||
| 22 | + # Subtest: top level it disabled | ||
| 23 | + ok 5 - top level it disabled # SKIP test name does not match pattern | ||
| 24 | + --- | ||
| 25 | + duration_ms: * | ||
| 26 | + ... | ||
| 27 | + # Subtest: top level skipped it disabled | ||
| 28 | + ok 6 - top level skipped it disabled # SKIP test name does not match pattern | ||
| 29 | + --- | ||
| 30 | + duration_ms: * | ||
| 31 | + ... | ||
| 32 | + # Subtest: top level skipped it enabled | ||
| 33 | + ok 7 - top level skipped it enabled # SKIP | ||
| 34 | + --- | ||
| 35 | + duration_ms: * | ||
| 36 | + ... | ||
| 37 | + # Subtest: top level describe disabled | ||
| 38 | + ok 8 - top level describe disabled # SKIP test name does not match pattern | ||
| 39 | + --- | ||
| 40 | + duration_ms: * | ||
| 41 | + ... | ||
| 42 | + # Subtest: top level skipped describe disabled | ||
| 43 | + ok 9 - top level skipped describe disabled # SKIP test name does not match pattern | ||
| 44 | + --- | ||
| 45 | + duration_ms: * | ||
| 46 | + ... | ||
| 47 | + # Subtest: top level skipped describe enabled | ||
| 48 | + ok 10 - top level skipped describe enabled # SKIP | ||
| 49 | + --- | ||
| 50 | + duration_ms: * | ||
| 51 | + ... | ||
| 52 | + # Subtest: top level runs because name includes PaTtErN | ||
| 53 | + ok 11 - top level runs because name includes PaTtErN | ||
| 54 | + --- | ||
| 55 | + duration_ms: * | ||
| 56 | + ... | ||
| 57 | + # Subtest: top level test enabled | ||
| 58 | + # Subtest: nested test runs because name includes PATTERN | ||
| 59 | + ok 1 - nested test runs because name includes PATTERN | ||
| 60 | + --- | ||
| 61 | + duration_ms: * | ||
| 62 | + ... | ||
| 63 | + 1..1 | ||
| 64 | + ok 12 - top level test enabled | ||
| 65 | + --- | ||
| 66 | + duration_ms: * | ||
| 67 | + ... | ||
| 68 | + # Subtest: top level describe enabled | ||
| 69 | + # Subtest: nested it disabled | ||
| 70 | + ok 1 - nested it disabled # SKIP test name does not match pattern | ||
| 71 | + --- | ||
| 72 | + duration_ms: * | ||
| 73 | + ... | ||
| 74 | + # Subtest: nested it enabled | ||
| 75 | + ok 2 - nested it enabled | ||
| 76 | + --- | ||
| 77 | + duration_ms: * | ||
| 78 | + ... | ||
| 79 | + # Subtest: nested describe disabled | ||
| 80 | + ok 3 - nested describe disabled # SKIP test name does not match pattern | ||
| 81 | + --- | ||
| 82 | + duration_ms: * | ||
| 83 | + ... | ||
| 84 | + # Subtest: nested describe enabled | ||
| 85 | + # Subtest: is enabled | ||
| 86 | + ok 1 - is enabled | ||
| 87 | + --- | ||
| 88 | + duration_ms: * | ||
| 89 | + ... | ||
| 90 | + 1..1 | ||
| 91 | + ok 4 - nested describe enabled | ||
| 92 | + --- | ||
| 93 | + duration_ms: * | ||
| 94 | + ... | ||
| 95 | + 1..4 | ||
| 96 | + ok 13 - top level describe enabled | ||
| 97 | + --- | ||
| 98 | + duration_ms: * | ||
| 99 | + ... | ||
| 100 | + 1..13 | ||
| 101 | + # tests 13 | ||
| 102 | + # pass 4 | ||
| 103 | + # fail 0 | ||
| 104 | + # cancelled 0 | ||
| 105 | + # skipped 9 | ||
| 106 | + # todo 0 | ||
| 107 | + # duration_ms * | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments