Skip to content

Commit d01dda7

Browse files
atlowChemiaduh95
authored andcommitted
test_runner: do not tag-filter test file wrappers
Under run({ testTagFilters, isolation: 'process' }) the parent process's FileTest wrappers have empty tag sets, so any include filter filtered out the wrappers themselves and no test file was ever spawned. The same applied to the single re-spawned child in watch mode with isolation 'none'. Exempt file wrappers from tag filtering: the filter is re-emitted to the child process and applied there, matching isolation 'none' results. This also removes the testTagFilterExpressions bookkeeping and the isolation-conditional assignment of testTagFilters, both of which existed only to keep the parent process from filtering its own file wrappers. The parent now always holds the canonical filter values and re-emits them to child processes. Refs: #63221 Signed-off-by: atlowChemi <chemi@atlow.co.il> PR-URL: #65170 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
1 parent c3b120e commit d01dda7

4 files changed

Lines changed: 32 additions & 24 deletions

File tree

lib/internal/test_runner/runner.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ function getRunArgs(path, { forceExit,
182182
inspectPort,
183183
testNamePatterns,
184184
testSkipPatterns,
185-
testTagFilterExpressions,
185+
testTagFilters,
186186
only,
187187
hasFiles,
188188
testFiles,
@@ -224,8 +224,8 @@ function getRunArgs(path, { forceExit,
224224
if (testSkipPatterns != null) {
225225
ArrayPrototypeForEach(testSkipPatterns, (pattern) => ArrayPrototypePush(runArgs, `--test-skip-pattern=${pattern}`));
226226
}
227-
if (testTagFilterExpressions != null) {
228-
ArrayPrototypeForEach(testTagFilterExpressions, (value) => ArrayPrototypePush(runArgs, `--experimental-test-tag-filter=${value}`));
227+
if (testTagFilters != null) {
228+
ArrayPrototypeForEach(testTagFilters, (value) => ArrayPrototypePush(runArgs, `--experimental-test-tag-filter=${value}`));
229229
}
230230
if (only === true) {
231231
ArrayPrototypePush(runArgs, '--test-only');
@@ -284,6 +284,14 @@ class FileTest extends Test {
284284
this.timeout = null;
285285
}
286286

287+
willBeFilteredByTags() {
288+
// File wrappers have no tags of their own. Tag filtering applies to the
289+
// tests inside the file, which run in a child process (or in-process
290+
// import); filtering the wrapper would prevent the file from running at
291+
// all.
292+
return false;
293+
}
294+
287295
#skipReporting() {
288296
return this.#reportedChildren > 0 && (!this.error || this.error.failureType === kSubtestsFailed);
289297
}
@@ -863,7 +871,6 @@ function run(options = kEmptyObject) {
863871
});
864872
}
865873

866-
let testTagFilterExpressions = null;
867874
if (testTagFilters != null) {
868875
if (!ArrayIsArray(testTagFilters)) {
869876
testTagFilters = [testTagFilters];
@@ -875,10 +882,8 @@ function run(options = kEmptyObject) {
875882
testTagFilters = ArrayPrototypeMap(testTagFilters, (value, i) => (
876883
validateAndCanonicalizeTagFilter(value, `options.testTagFilters[${i}]`)
877884
));
878-
testTagFilterExpressions = testTagFilters;
879885
}
880886
}
881-
testTagFilterExpressions ??= options.testTagFilterExpressions;
882887

883888
validateOneOf(isolation, 'options.isolation', ['process', 'none']);
884889
validateBoolean(coverage, 'options.coverage');
@@ -979,7 +984,6 @@ function run(options = kEmptyObject) {
979984
testNamePatterns,
980985
testSkipPatterns,
981986
testTagFilters,
982-
testTagFilterExpressions,
983987
hasFiles: files != null,
984988
globPatterns,
985989
only,

lib/internal/test_runner/test.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,7 @@ class Test extends AsyncResource {
656656
}
657657

658658
if (isFilteringByTags) {
659-
this.filteredByTag = !evaluateTagFilters(config.testTagFilters, this.tagSet);
659+
this.filteredByTag = this.willBeFilteredByTags();
660660
if (!this.filteredByTag) {
661661
for (let t = this.parent; t !== null && t.filteredByTag; t = t.parent) {
662662
t.filteredByTag = false;
@@ -894,6 +894,10 @@ class Test extends AsyncResource {
894894
return false;
895895
}
896896

897+
willBeFilteredByTags() {
898+
return !evaluateTagFilters(this.config.testTagFilters, this.tagSet);
899+
}
900+
897901
/**
898902
* Returns a name of the test prefixed by name of all its ancestors in ascending order, separated by a space
899903
* Ex."grandparent parent test"

lib/internal/test_runner/utils.js

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,6 @@ function parseCommandLine() {
272272
let testNamePatterns = mapPatternFlagToRegExArray('--test-name-pattern');
273273
let testSkipPatterns = mapPatternFlagToRegExArray('--test-skip-pattern');
274274
let testTagFilters = null;
275-
let testTagFilterExpressions = null;
276275

277276
if (isChildProcessV8) {
278277
kBuiltinReporters.set('v8-serializer', 'internal/test_runner/reporter/v8-serializer');
@@ -308,19 +307,14 @@ function parseCommandLine() {
308307
const tagFilterFlag = getOptionValue('--experimental-test-tag-filter');
309308
if (tagFilterFlag?.length > 0) {
310309
emitExperimentalWarning('Test tags');
311-
testTagFilterExpressions = tagFilterFlag;
312-
// Validate at parent startup so a malformed flag fails fast,
313-
// independent of isolation mode. Under isolation='process' the
314-
// validated strings go unused at the parent (children re-validate
315-
// and apply the filter); the validation here only surfaces input
316-
// errors early.
317-
const validated = ArrayPrototypeMap(
310+
// File wrappers are exempt from tag filtering, so holding the filters
311+
// in the parent is safe under any isolation mode; under
312+
// isolation='process' the canonical values are re-emitted to the
313+
// child processes, which apply the filter themselves.
314+
testTagFilters = ArrayPrototypeMap(
318315
tagFilterFlag,
319316
(value, i) => validateAndCanonicalizeTagFilter(value, `--experimental-test-tag-filter[${i}]`),
320317
);
321-
if (isolation === 'none') {
322-
testTagFilters = validated;
323-
}
324318
}
325319

326320
if (isolation === 'none') {
@@ -364,7 +358,6 @@ function parseCommandLine() {
364358
const tagFilterFlag = getOptionValue('--experimental-test-tag-filter');
365359
if (tagFilterFlag?.length > 0) {
366360
emitExperimentalWarning('Test tags');
367-
testTagFilterExpressions = tagFilterFlag;
368361
testTagFilters = ArrayPrototypeMap(
369362
tagFilterFlag,
370363
(value, i) => validateAndCanonicalizeTagFilter(value, `--experimental-test-tag-filter[${i}]`),
@@ -431,7 +424,6 @@ function parseCommandLine() {
431424
sourceMaps,
432425
testNamePatterns,
433426
testSkipPatterns,
434-
testTagFilterExpressions,
435427
testTagFilters,
436428
timeout,
437429
updateSnapshots,

test/parallel/test-runner-tags-events.mjs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,22 @@ describe('tag-bearing event payloads', { concurrency: false }, () => {
8787
});
8888

8989
it('test:pass fires only for selected tagged tests when filtered', async () => {
90-
// isolation='none' so the parent applies the filter directly. Under
91-
// 'process', the FileTest wrapper (which has no tags) would itself be
92-
// filtered out by the include filter - same wart as --test-name-pattern.
9390
const stream = run({ files: [fixture], testTagFilters: ['db'], isolation: 'none' });
9491
stream.on('test:fail', common.mustNotCall());
9592
// 3 db-tagged tests pass + the db suite itself.
9693
stream.on('test:pass', common.mustCall(4));
9794
// eslint-disable-next-line no-unused-vars
9895
for await (const _ of stream);
9996
});
97+
98+
it('filtering under process isolation runs the file and filters inside it', async () => {
99+
// The FileTest wrapper has no tags and must not be filtered out itself;
100+
// the filter is re-emitted to the child process and applied there.
101+
const stream = run({ files: [fixture], testTagFilters: ['db'], isolation: 'process' });
102+
stream.on('test:fail', common.mustNotCall());
103+
// 3 db-tagged tests pass + the db suite itself.
104+
stream.on('test:pass', common.mustCall(4));
105+
// eslint-disable-next-line no-unused-vars
106+
for await (const _ of stream);
107+
});
100108
});

0 commit comments

Comments
 (0)