Skip to content

Commit f47b2b0

Browse files
committed
test_runner: improve --test-name-pattern to allow matching single test
Try to match a test by name prefixed with all its ancestors to ensure uniqueness of the name Fixes: nodejs#46728
1 parent af3e2b2 commit f47b2b0

File tree

4 files changed

+104
-8
lines changed

4 files changed

+104
-8
lines changed

doc/api/test.md

+17
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,23 @@ allows regular expression flags to be used. In the previous example, starting
267267
Node.js with `--test-name-pattern="/test [4-5]/i"` would match `Test 4` and
268268
`Test 5` because the pattern is case-insensitive.
269269

270+
To match a single test with a pattern, you can prefix it with all its ancestor
271+
test names separated by space, to ensure it is unique.
272+
For example, given the following test file:
273+
274+
```js
275+
describe('test 1', (t) => {
276+
it('some test');
277+
});
278+
279+
describe('test 2', (t) => {
280+
it('some test');
281+
});
282+
```
283+
284+
Starting Node.js with `--test-name-pattern="test 1 some test"` would match
285+
only `some test` in `test 1`.
286+
270287
Test name patterns do not change the set of files that the test runner executes.
271288

272289
## Extraneous asynchronous activity

lib/internal/test_runner/test.js

+33-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22
const {
3+
Array,
4+
ArrayPrototypeJoin,
35
ArrayPrototypePush,
46
ArrayPrototypePushApply,
57
ArrayPrototypeReduce,
@@ -362,8 +364,37 @@ class Test extends AsyncResource {
362364
}
363365

364366
matchesTestNamePatterns() {
365-
return ArrayPrototypeSome(testNamePatterns, (re) => RegExpPrototypeExec(re, this.name) !== null) ||
366-
this.parent?.matchesTestNamePatterns();
367+
const matchesByNameOrParent = ArrayPrototypeSome(testNamePatterns, (re) =>
368+
RegExpPrototypeExec(re, this.name) !== null ||
369+
this.parent?.matchesTestNamePatterns(),
370+
);
371+
372+
if (matchesByNameOrParent) return true;
373+
374+
const testNameWithAncestors = this.getTestNameWithAncestors();
375+
if (!testNameWithAncestors) return false;
376+
377+
return ArrayPrototypeSome(testNamePatterns, (re) => RegExpPrototypeExec(re, testNameWithAncestors) !== null);
378+
}
379+
380+
/**
381+
* Returns a name of the test prefixed by name of all its ancestors in ascending order, separated by a space
382+
* Ex."grandparent parent test"
383+
*
384+
* It's needed to match a single test with non-unique name by pattern
385+
*/
386+
getTestNameWithAncestors() {
387+
if (!this.nesting) return;
388+
389+
const ancestorNames = Array(this.nesting);
390+
let parent = this.parent;
391+
392+
for (let i = 0; i < this.nesting; i++) {
393+
ancestorNames[this.nesting - i - 1] = parent.name;
394+
parent = parent.parent;
395+
}
396+
397+
return `${ArrayPrototypeJoin(ancestorNames, ' ')} ${this.name}`;
367398
}
368399

369400
hasConcurrency() {

test/fixtures/test-runner/output/name_pattern.js

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Flags: --test-name-pattern=enabled --test-name-pattern=yes --test-name-pattern=/pattern/i
1+
// Flags: --test-name-pattern=enabled --test-name-pattern=yes --test-name-pattern=/pattern/i --test-name-pattern=/^DescribeForMatchWithAncestors\sNestedDescribeForMatchWithAncestors\sNestedTest$/
22
'use strict';
33
const common = require('../../../common');
44
const {
@@ -65,3 +65,15 @@ describe('no', function() {
6565
it('yes', () => {});
6666
});
6767
});
68+
69+
describe('DescribeForMatchWithAncestors', () => {
70+
it('NestedTest', () => common.mustNotCall());
71+
72+
describe('NestedDescribeForMatchWithAncestors', () => {
73+
it('NestedTest', common.mustCall());
74+
});
75+
})
76+
77+
describe('DescribeForMatchWithAncestors', () => {
78+
it('NestedTest', () => common.mustNotCall());
79+
})

test/fixtures/test-runner/output/name_pattern.snapshot

+41-5
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,48 @@ ok 15 - no
171171
duration_ms: *
172172
type: 'suite'
173173
...
174-
1..15
175-
# tests 21
176-
# suites 10
177-
# pass 13
174+
# Subtest: DescribeForMatchWithAncestors
175+
# Subtest: NestedTest
176+
ok 1 - NestedTest # SKIP test name does not match pattern
177+
---
178+
duration_ms: *
179+
...
180+
# Subtest: NestedDescribeForMatchWithAncestors
181+
# Subtest: NestedTest
182+
ok 1 - NestedTest
183+
---
184+
duration_ms: *
185+
...
186+
1..1
187+
ok 2 - NestedDescribeForMatchWithAncestors
188+
---
189+
duration_ms: *
190+
type: 'suite'
191+
...
192+
1..2
193+
ok 16 - DescribeForMatchWithAncestors
194+
---
195+
duration_ms: *
196+
type: 'suite'
197+
...
198+
# Subtest: DescribeForMatchWithAncestors
199+
# Subtest: NestedTest
200+
ok 1 - NestedTest # SKIP test name does not match pattern
201+
---
202+
duration_ms: *
203+
...
204+
1..1
205+
ok 17 - DescribeForMatchWithAncestors
206+
---
207+
duration_ms: *
208+
type: 'suite'
209+
...
210+
1..17
211+
# tests 24
212+
# suites 13
213+
# pass 14
178214
# fail 0
179215
# cancelled 0
180-
# skipped 8
216+
# skipped 10
181217
# todo 0
182218
# duration_ms *

0 commit comments

Comments
 (0)