Skip to content

Commit

Permalink
test_runner: propagate only to test ancestors
Browse files Browse the repository at this point in the history
  • Loading branch information
MoLow committed Aug 16, 2023
1 parent a88ec28 commit a08c687
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 115 deletions.
42 changes: 29 additions & 13 deletions lib/internal/test_runner/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,8 @@ class Test extends AsyncResource {
if (parent === null) {
this.concurrency = 1;
this.nesting = 0;
this.only = testOnlyFlag;
this.reporter = new TestsStream();
this.runOnlySubtests = this.only;
this.runOnlySubtests = testOnlyFlag;
this.testNumber = 0;
this.timeout = kDefaultTimeout;
this.root = this;
Expand All @@ -249,9 +248,8 @@ class Test extends AsyncResource {

this.concurrency = parent.concurrency;
this.nesting = nesting;
this.only = only ?? !parent.runOnlySubtests;
this.reporter = parent.reporter;
this.runOnlySubtests = !this.only;
this.runOnlySubtests = false;
this.testNumber = parent.subtests.length + 1;
this.timeout = parent.timeout;
this.root = parent.root;
Expand Down Expand Up @@ -296,10 +294,6 @@ class Test extends AsyncResource {
skip = 'test name does not match pattern';
}

if (testOnlyFlag && !this.only) {
skip = '\'only\' option not set';
}

if (skip) {
fn = noop;
}
Expand Down Expand Up @@ -338,10 +332,11 @@ class Test extends AsyncResource {
this.subtests = [];
this.waitingOn = 0;
this.finished = false;
this.only = testOnlyFlag || parent?.runOnlySubtests ? only : undefined;

if (!testOnlyFlag && (only || this.runOnlySubtests)) {
const warning =
"'only' and 'runOnly' require the --test-only command-line option.";

if (!testOnlyFlag && only && !parent.runOnlySubtests) {
const warning = "'only' requires the --test-only command-line option.";
this.diagnostic(warning);
}

Expand All @@ -355,6 +350,18 @@ class Test extends AsyncResource {
file: loc[2],
};
}

if (this.only && parent !== null) {
parent.markOnly();
}
}

markOnly() {
if (this.runOnlySubtests) {
return;
}
this.runOnlySubtests = true;
this.parent?.markOnly();
}

matchesTestNamePatterns() {
Expand Down Expand Up @@ -580,9 +587,18 @@ class Test extends AsyncResource {
}
}

get runOnlySibling() {
return this.parent?.runOnlySubtests && !this.only && !this.runOnlySubtests;
}

async run() {
this.startTime = hrtime();

if (this.runOnlySibling || this.only === false) {
this.fn = noop;
this.skip('\'only\' option not set');
}

if (this[kShouldAbort]()) {
this.postRun();
return;
Expand Down Expand Up @@ -893,7 +909,6 @@ class Suite extends Test {
this.fn = options.fn || this.fn;
this.skipped = false;
}
this.runOnlySubtests = testOnlyFlag;

try {
const { ctx, args } = this.getRunArgs();
Expand All @@ -915,7 +930,7 @@ class Suite extends Test {

this.buildPhaseFinished = true;
}
this.fn = () => {};
this.fn = noop;
}

getRunArgs() {
Expand All @@ -932,6 +947,7 @@ class Suite extends Test {

async run() {
const hookArgs = this.getRunArgs();
this.runOnlySubtests ||= this.runOnlySibling;

let stopPromise;
try {
Expand Down
118 changes: 57 additions & 61 deletions test/fixtures/test-runner/output/only_tests.js
Original file line number Diff line number Diff line change
@@ -1,100 +1,96 @@
// Flags: --test-only
'use strict';
require('../../../common');
const common = require('../../../common');
const { test, describe, it } = require('node:test');

// These tests should be skipped based on the 'only' option.
test('only = undefined');
test('only = undefined, skip = string', { skip: 'skip message' });
test('only = undefined, skip = true', { skip: true });
test('only = undefined, skip = false', { skip: false });
test('only = false', { only: false });
test('only = false, skip = string', { only: false, skip: 'skip message' });
test('only = false, skip = true', { only: false, skip: true });
test('only = false, skip = false', { only: false, skip: false });
test('only = undefined', common.mustNotCall());
test('only = undefined, skip = string', { skip: 'skip message' }, common.mustNotCall());
test('only = undefined, skip = true', { skip: true }, common.mustNotCall());
test('only = undefined, skip = false', { skip: false }, common.mustNotCall());
test('only = false', { only: false }, common.mustNotCall());
test('only = false, skip = string', { only: false, skip: 'skip message' }, common.mustNotCall());
test('only = false, skip = true', { only: false, skip: true }, common.mustNotCall());
test('only = false, skip = false', { only: false, skip: false }, common.mustNotCall());

// These tests should be skipped based on the 'skip' option.
test('only = true, skip = string', { only: true, skip: 'skip message' });
test('only = true, skip = true', { only: true, skip: true });
test('only = true, skip = string', { only: true, skip: 'skip message' }, common.mustNotCall());
test('only = true, skip = true', { only: true, skip: true }, common.mustNotCall());

// An 'only' test with subtests.
test('only = true, with subtests', { only: true }, async (t) => {
test('only = true, with subtests', { only: true }, common.mustCall(async (t) => {
// These subtests should run.
await t.test('running subtest 1');
await t.test('running subtest 2');
await t.test('running subtest 1', common.mustCall());
await t.test('running subtest 2', common.mustCall());

// Switch the context to only execute 'only' tests.
t.runOnly(true);
await t.test('skipped subtest 1');
await t.test('skipped subtest 2');
await t.test('running subtest 3', { only: true });
await t.test('skipped subtest 1', common.mustNotCall());
await t.test('skipped subtest 2'), common.mustNotCall();
await t.test('running subtest 3', { only: true }, common.mustCall());

// Switch the context back to execute all tests.
t.runOnly(false);
await t.test('running subtest 4', async (t) => {
await t.test('running subtest 4', common.mustCall(async (t) => {
// These subtests should run.
await t.test('running sub-subtest 1');
await t.test('running sub-subtest 2');
await t.test('running sub-subtest 1', common.mustCall());
await t.test('running sub-subtest 2', common.mustCall());

// Switch the context to only execute 'only' tests.
t.runOnly(true);
await t.test('skipped sub-subtest 1');
await t.test('skipped sub-subtest 2');
});
await t.test('skipped sub-subtest 1', common.mustNotCall());
await t.test('skipped sub-subtest 2', common.mustNotCall());
}));

// Explicitly do not run these tests.
await t.test('skipped subtest 3', { only: false });
await t.test('skipped subtest 4', { skip: true });
});
await t.test('skipped subtest 3', { only: false }, common.mustNotCall());
await t.test('skipped subtest 4', { skip: true }, common.mustNotCall());
}));

describe.only('describe only = true, with subtests', () => {
it.only('`it` subtest 1 should run', () => {});
describe.only('describe only = true, with subtests', common.mustCall(() => {
it.only('`it` subtest 1 should run', common.mustCall());

it('`it` subtest 2 should not run', async () => {});
});
it('`it` subtest 2 should not run', common.mustNotCall());
}));

describe.only('describe only = true, with a mixture of subtests', () => {
it.only('`it` subtest 1', () => {});
describe.only('describe only = true, with a mixture of subtests', common.mustCall(() => {
it.only('`it` subtest 1', common.mustCall());

it.only('`it` async subtest 1', async () => {});
it.only('`it` async subtest 1', common.mustCall(async () => {}));

it('`it` subtest 2 only=true', { only: true });
it('`it` subtest 2 only=true', { only: true }, common.mustCall());

it('`it` subtest 2 only=false', { only: false }, () => {
throw new Error('This should not run');
});
it('`it` subtest 2 only=false', { only: false }, common.mustNotCall());

it.skip('`it` subtest 3 skip', () => {
throw new Error('This should not run');
});
it.skip('`it` subtest 3 skip', common.mustNotCall());

it.todo('`it` subtest 4 todo', { only: false }, () => {
throw new Error('This should not run');
});
it.todo('`it` subtest 4 todo', { only: false }, common.mustNotCall());

test.only('`test` subtest 1', () => {});
test.only('`test` subtest 1', common.mustCall());

test.only('`test` async subtest 1', async () => {});
test.only('`test` async subtest 1', common.mustCall(async () => {}));

test('`test` subtest 2 only=true', { only: true });
test('`test` subtest 2 only=true', { only: true }, common.mustCall());

test('`test` subtest 2 only=false', { only: false }, () => {
throw new Error('This should not run');
});
test('`test` subtest 2 only=false', { only: false }, common.mustNotCall());

test.skip('`test` subtest 3 skip', () => {
throw new Error('This should not run');
});
test.skip('`test` subtest 3 skip', common.mustNotCall());

test.todo('`test` subtest 4 todo', { only: false }, () => {
throw new Error('This should not run');
});
});
test.todo('`test` subtest 4 todo', { only: false }, common.mustNotCall());
}));

describe.only('describe only = true, with subtests', () => {
test.only('subtest should run', () => {});
describe.only('describe only = true, with subtests', common.mustCall(() => {
test.only('subtest should run', common.mustCall());

test('async subtest should not run', async () => {});
test('async subtest should not run', common.mustNotCall());

test('subtest should be skipped', { only: false }, () => {});
});
test('subtest should be skipped', { only: false }, common.mustNotCall());
}));

describe('describe only = undefined, with subtests', common.mustCall(() => {
test('async subtest should not run', common.mustNotCall());
}));

describe('describe only = false, with subtests', { only: false }, common.mustCall(() => {
test('async subtest should not run', common.mustNotCall());
}));
32 changes: 28 additions & 4 deletions test/fixtures/test-runner/output/only_tests.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,36 @@ ok 14 - describe only = true, with subtests
duration_ms: *
type: 'suite'
...
1..14
# tests 40
# suites 3
# Subtest: describe only = undefined, with subtests
# Subtest: async subtest should not run
ok 1 - async subtest should not run # SKIP 'only' option not set
---
duration_ms: *
...
1..1
ok 15 - describe only = undefined, with subtests
---
duration_ms: *
type: 'suite'
...
# Subtest: describe only = false, with subtests
# Subtest: async subtest should not run
ok 1 - async subtest should not run # SKIP 'only' option not set
---
duration_ms: *
...
1..1
ok 16 - describe only = false, with subtests
---
duration_ms: *
type: 'suite'
...
1..16
# tests 42
# suites 5
# pass 15
# fail 0
# cancelled 0
# skipped 25
# skipped 27
# todo 0
# duration_ms *
6 changes: 3 additions & 3 deletions test/fixtures/test-runner/output/output.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,11 +274,11 @@ test('callback async throw after done', (t, done) => {
done();
});

test('only is set but not in only mode', { only: true }, async (t) => {
// All of these subtests should run.
test('runOnly is set', async (t) => {
// Subtests should run only outside of a runOnly block, unless they have only: true.
await t.test('running subtest 1');
t.runOnly(true);
await t.test('running subtest 2');
await t.test('skipped subtest 2');
await t.test('running subtest 3', { only: true });
t.runOnly(false);
await t.test('running subtest 4');
Expand Down
15 changes: 6 additions & 9 deletions test/fixtures/test-runner/output/output.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -499,35 +499,32 @@ ok 48 - callback async throw after done
---
duration_ms: *
...
# Subtest: only is set but not in only mode
# Subtest: runOnly is set
# Subtest: running subtest 1
ok 1 - running subtest 1
---
duration_ms: *
...
# Subtest: running subtest 2
ok 2 - running subtest 2
# Subtest: skipped subtest 2
ok 2 - skipped subtest 2 # SKIP 'only' option not set
---
duration_ms: *
...
# 'only' and 'runOnly' require the --test-only command-line option.
# Subtest: running subtest 3
ok 3 - running subtest 3
---
duration_ms: *
...
# 'only' and 'runOnly' require the --test-only command-line option.
# Subtest: running subtest 4
ok 4 - running subtest 4
---
duration_ms: *
...
1..4
ok 49 - only is set but not in only mode
ok 49 - runOnly is set
---
duration_ms: *
...
# 'only' and 'runOnly' require the --test-only command-line option.
# Subtest: custom inspect symbol fail
not ok 50 - custom inspect symbol fail
---
Expand Down Expand Up @@ -733,9 +730,9 @@ not ok 62 - invalid subtest fail
# Warning: Test "callback async throw after done" generated asynchronous activity after the test ended. This activity created the error "Error: thrown from callback async throw after done" and would have caused the test to fail, but instead triggered an uncaughtException event.
# tests 76
# suites 0
# pass 35
# pass 34
# fail 25
# cancelled 3
# skipped 9
# skipped 10
# todo 4
# duration_ms *
Loading

0 comments on commit a08c687

Please sign in to comment.