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 Jan 27, 2024
1 parent 09da597 commit c1d34d1
Show file tree
Hide file tree
Showing 4 changed files with 106 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
37 changes: 35 additions & 2 deletions lib/internal/test_runner/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -362,8 +362,41 @@ 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() {
const ancestorNames = [];
let parent = this.parent;

for (let i = 0; i < this.nesting; i++) {
ArrayPrototypePush(ancestorNames, parent.name);
parent = parent.parent;
}

const formattedTestNameWithAncestors = `${ancestorNames.reverse().join(' ')} ${this.name}`;

return ancestorNames.length ? formattedTestNameWithAncestors : undefined;
}

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=/^DescribeForFullPath\sNestedDescribeForFullPath\sNestedTest$/
'use strict';
const common = require('../../../common');
const {
Expand Down Expand Up @@ -65,3 +65,15 @@ describe('no', function() {
it('yes', () => {});
});
});

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

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

describe('DescribeForFullPath', () => {
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: DescribeForFullPath
# Subtest: NestedTest
ok 1 - NestedTest # SKIP test name does not match pattern
---
duration_ms: *
...
# Subtest: NestedDescribeForFullPath
# Subtest: NestedTest
ok 1 - NestedTest
---
duration_ms: *
...
1..1
ok 2 - NestedDescribeForFullPath
---
duration_ms: *
type: 'suite'
...
1..2
ok 16 - DescribeForFullPath
---
duration_ms: *
type: 'suite'
...
# Subtest: DescribeForFullPath
# Subtest: NestedTest
ok 1 - NestedTest # SKIP test name does not match pattern
---
duration_ms: *
...
1..1
ok 17 - DescribeForFullPath
---
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 c1d34d1

Please sign in to comment.