Skip to content

Commit

Permalink
test_runner: improve --test-name-pattern to allow matching single test
Browse files Browse the repository at this point in the history
Try to match a test by name prefixed with all its ancestors
to ensure uniqueness of the name

Fixes: nodejs#46728
  • Loading branch information
mdrobny committed Feb 29, 2024
1 parent 3ef7117 commit 8d5e973
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 8 deletions.
17 changes: 17 additions & 0 deletions doc/api/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,23 @@ allows regular expression flags to be used. In the previous example, starting
Node.js with `--test-name-pattern="/test [4-5]/i"` would match `Test 4` and
`Test 5` because the pattern is case-insensitive.

To match a single test with a pattern, you can prefix it with all its ancestor
test names separated by space, to ensure it is unique.
For example, given the following test file:

```js
describe('test 1', (t) => {
it('some test');
});

describe('test 2', (t) => {
it('some test');
});
```

Starting Node.js with `--test-name-pattern="test 1 some test"` would match
only `some test` in `test 1`.

Test name patterns do not change the set of files that the test runner executes.

## Extraneous asynchronous activity
Expand Down
35 changes: 33 additions & 2 deletions lib/internal/test_runner/test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';
const {
Array,
ArrayPrototypeJoin,
ArrayPrototypePush,
ArrayPrototypePushApply,
ArrayPrototypeReduce,
Expand Down Expand Up @@ -362,8 +364,37 @@ class Test extends AsyncResource {
}

matchesTestNamePatterns() {
return ArrayPrototypeSome(testNamePatterns, (re) => RegExpPrototypeExec(re, this.name) !== null) ||
this.parent?.matchesTestNamePatterns();
const matchesByNameOrParent = ArrayPrototypeSome(testNamePatterns, (re) =>
RegExpPrototypeExec(re, this.name) !== null,
) ||
this.parent?.matchesTestNamePatterns();

if (matchesByNameOrParent) return true;

const testNameWithAncestors = this.getTestNameWithAncestors();
if (!testNameWithAncestors) return false;

return ArrayPrototypeSome(testNamePatterns, (re) => RegExpPrototypeExec(re, testNameWithAncestors) !== null);
}

/**
* Returns a name of the test prefixed by name of all its ancestors in ascending order, separated by a space
* Ex."grandparent parent test"
*
* It's needed to match a single test with non-unique name by pattern
*/
getTestNameWithAncestors() {
if (!this.nesting) return;

const ancestorNames = Array(this.nesting);
let parent = this.parent;

for (let i = 0; i < this.nesting; i++) {
ancestorNames[this.nesting - i - 1] = parent.name;
parent = parent.parent;
}

return `${ArrayPrototypeJoin(ancestorNames, ' ')} ${this.name}`;
}

hasConcurrency() {
Expand Down
14 changes: 13 additions & 1 deletion test/fixtures/test-runner/output/name_pattern.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Flags: --test-name-pattern=enabled --test-name-pattern=yes --test-name-pattern=/pattern/i
// Flags: --test-name-pattern=enabled --test-name-pattern=yes --test-name-pattern=/pattern/i --test-name-pattern=/^DescribeForMatchWithAncestors\sNestedDescribeForMatchWithAncestors\sNestedTest$/
'use strict';
const common = require('../../../common');
const {
Expand Down Expand Up @@ -65,3 +65,15 @@ describe('no', function() {
it('yes', () => {});
});
});

describe('DescribeForMatchWithAncestors', () => {
it('NestedTest', () => common.mustNotCall());

describe('NestedDescribeForMatchWithAncestors', () => {
it('NestedTest', common.mustCall());
});
})

describe('DescribeForMatchWithAncestors', () => {
it('NestedTest', () => common.mustNotCall());
})
46 changes: 41 additions & 5 deletions test/fixtures/test-runner/output/name_pattern.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,48 @@ ok 15 - no
duration_ms: *
type: 'suite'
...
1..15
# tests 21
# suites 10
# pass 13
# Subtest: DescribeForMatchWithAncestors
# Subtest: NestedTest
ok 1 - NestedTest # SKIP test name does not match pattern
---
duration_ms: *
...
# Subtest: NestedDescribeForMatchWithAncestors
# Subtest: NestedTest
ok 1 - NestedTest
---
duration_ms: *
...
1..1
ok 2 - NestedDescribeForMatchWithAncestors
---
duration_ms: *
type: 'suite'
...
1..2
ok 16 - DescribeForMatchWithAncestors
---
duration_ms: *
type: 'suite'
...
# Subtest: DescribeForMatchWithAncestors
# Subtest: NestedTest
ok 1 - NestedTest # SKIP test name does not match pattern
---
duration_ms: *
...
1..1
ok 17 - DescribeForMatchWithAncestors
---
duration_ms: *
type: 'suite'
...
1..17
# tests 24
# suites 13
# pass 14
# fail 0
# cancelled 0
# skipped 8
# skipped 10
# todo 0
# duration_ms *

0 comments on commit 8d5e973

Please sign in to comment.