Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test_runner: call abort on test finish #48827

Merged
merged 12 commits into from
Jul 21, 2023
11 changes: 11 additions & 0 deletions lib/internal/test_runner/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -586,10 +586,21 @@ class Test extends AsyncResource {
return;
}

// Do not run for hooks and root test as hooks instance are shared between tests suite so aborting them will
// abort cause them to not run for further tests.
if (this.parent !== null) {
this.#abortController.abort();
}

await afterEach();
await after();
this.pass();
} catch (err) {
// Do not run for hooks and root test as hooks instance are shared between tests suite so aborting them will
// abort cause them to not run for further tests.
if (this.parent !== null) {
this.#abortController.abort();
}
try { await afterEach(); } catch { /* test is already failing, let's ignore the error */ }
try { await after(); } catch { /* Ignore error. */ }
if (isTestFailureError(err)) {
Expand Down
25 changes: 25 additions & 0 deletions test/fixtures/test-runner/aborts/failed-test-still-call-abort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const {test, afterEach} = require('node:test');
const assert = require('node:assert');

let testCount = 0;
let signal;

afterEach(() => {
testCount++;
assert.equal(signal.aborted, true);

console.log(`abort called for test ${testCount}`)
});

test("sync", (t) => {
signal = t.signal;
assert.equal(signal.aborted, false);
throw new Error('failing the sync test');
});

test("async", async (t) => {
await null;
signal = t.signal;
assert.equal(signal.aborted, false);
throw new Error('failing the async test');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const {test, afterEach} = require('node:test');
const assert = require('node:assert');

let testCount = 0;
let signal;

afterEach(() => {
testCount++;
assert.equal(signal.aborted, true);

console.log(`abort called for test ${testCount}`)
});

test("sync", (t) => {
signal = t.signal;
assert.equal(signal.aborted, false);
});

test("async", async (t) => {
await null;
signal = t.signal;
assert.equal(signal.aborted, false);
});
97 changes: 79 additions & 18 deletions test/parallel/test-runner-run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -118,21 +118,6 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
assert.strictEqual(result[5], 'ok 2 - this should be executed\n');
});

it('should stop watch mode when abortSignal aborts', async () => {
const controller = new AbortController();
const result = await run({ files: [join(testFixtures, 'test/random.cjs')], watch: true, signal: controller.signal })
.compose(async function* (source) {
for await (const chunk of source) {
if (chunk.type === 'test:pass') {
controller.abort();
yield chunk.data.name;
}
}
})
.toArray();
assert.deepStrictEqual(result, ['this should pass']);
});

it('should emit "test:watch:drained" event on watch mode', async () => {
const controller = new AbortController();
await run({ files: [join(testFixtures, 'test/random.cjs')], watch: true, signal: controller.signal })
Expand All @@ -143,6 +128,82 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
});
});

describe('AbortSignal', () => {
it('should stop watch mode when abortSignal aborts', async () => {
MoLow marked this conversation as resolved.
Show resolved Hide resolved
const controller = new AbortController();
const result = await run({
files: [join(testFixtures, 'test/random.cjs')],
watch: true,
signal: controller.signal
})
.compose(async function* (source) {
for await (const chunk of source) {
if (chunk.type === 'test:pass') {
controller.abort();
yield chunk.data.name;
}
}
})
.toArray();
assert.deepStrictEqual(result, ['this should pass']);
});

it('should abort when test succeeded', async () => {
const stream = run({
files: [fixtures.path('test-runner', 'aborts', 'successful-test-still-call-abort.js')]
});

let passedTestCount = 0;
let failedTestCount = 0;

let output = '';
for await (const data of stream) {
if (data.type === 'test:stdout') {
output += data.data.message.toString();
}
if (data.type === 'test:fail') {
failedTestCount++;
}
if (data.type === 'test:pass') {
passedTestCount++;
}
}

assert.match(output, /abort called for test 1/);
assert.match(output, /abort called for test 2/);
assert.strictEqual(failedTestCount, 0, new Error('no tests should fail'));
assert.strictEqual(passedTestCount, 2);
});

it('should abort when test failed', async () => {
const stream = run({
files: [fixtures.path('test-runner', 'aborts', 'failed-test-still-call-abort.js')]
});

let passedTestCount = 0;
let failedTestCount = 0;

let output = '';
for await (const data of stream) {
if (data.type === 'test:stdout') {
output += data.data.message.toString();
}
if (data.type === 'test:fail') {
failedTestCount++;
}
if (data.type === 'test:pass') {
passedTestCount++;
}
}

assert.match(output, /abort called for test 1/);
assert.match(output, /abort called for test 2/);
assert.strictEqual(passedTestCount, 0, new Error('no tests should pass'));
assert.strictEqual(failedTestCount, 2);
});

});

describe('sharding', () => {
const shardsTestsFixtures = fixtures.path('test-runner', 'shards');
const shardsTestsFiles = [
Expand Down Expand Up @@ -256,7 +317,7 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
executedTestFiles.push(passedTest.file);
});
// eslint-disable-next-line no-unused-vars
for await (const _ of stream) ;
for await (const _ of stream);

assert.deepStrictEqual(executedTestFiles, [
join(shardsTestsFixtures, 'a.cjs'),
Expand Down Expand Up @@ -286,7 +347,7 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {

await Promise.all(testStreams.map(async (stream) => {
// eslint-disable-next-line no-unused-vars
for await (const _ of stream) ;
for await (const _ of stream);
}));

assert.deepStrictEqual(executedTestFiles, [...new Set(executedTestFiles)]);
Expand Down Expand Up @@ -314,7 +375,7 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {

await Promise.all(testStreams.map(async (stream) => {
// eslint-disable-next-line no-unused-vars
for await (const _ of stream) ;
for await (const _ of stream);
}));

assert.deepStrictEqual(executedTestFiles.sort(), [...shardsTestsFiles].sort());
Expand Down