Skip to content

Commit 22606ae

Browse files
rluvatonpluris
authored and
pluris
committed
test_runner: call abort on test finish
PR-URL: nodejs#48827 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
1 parent 0480818 commit 22606ae

File tree

5 files changed

+166
-18
lines changed

5 files changed

+166
-18
lines changed

lib/internal/test_runner/test.js

+6
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,12 @@ class Test extends AsyncResource {
601601
} else {
602602
this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure));
603603
}
604+
} finally {
605+
// Do not abort hooks and the root test as hooks instance are shared between tests suite so aborting them will
606+
// cause them to not run for further tests.
607+
if (this.parent !== null) {
608+
this.#abortController.abort();
609+
}
604610
}
605611

606612
// Clean up the test. Then, try to report the results and execute any
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const {test, afterEach} = require('node:test');
2+
const assert = require('node:assert');
3+
const { waitForAbort } = require('./wait-for-abort-helper');
4+
5+
let testCount = 0;
6+
let signal;
7+
8+
afterEach(() => {
9+
assert.equal(signal.aborted, false);
10+
11+
waitForAbort({ testNumber: ++testCount, signal });
12+
});
13+
14+
test("sync", (t) => {
15+
signal = t.signal;
16+
assert.equal(signal.aborted, false);
17+
throw new Error('failing the sync test');
18+
});
19+
20+
test("async", async (t) => {
21+
await null;
22+
signal = t.signal;
23+
assert.equal(signal.aborted, false);
24+
throw new Error('failing the async test');
25+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const {test, afterEach} = require('node:test');
2+
const assert = require('node:assert');
3+
const {waitForAbort} = require("./wait-for-abort-helper");
4+
5+
let testCount = 0;
6+
let signal;
7+
8+
afterEach(() => {
9+
assert.equal(signal.aborted, false);
10+
11+
waitForAbort({ testNumber: ++testCount, signal });
12+
});
13+
14+
test("sync", (t) => {
15+
signal = t.signal;
16+
assert.equal(signal.aborted, false);
17+
});
18+
19+
test("async", async (t) => {
20+
await null;
21+
signal = t.signal;
22+
assert.equal(signal.aborted, false);
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module.exports = {
2+
waitForAbort: function ({ testNumber, signal }) {
3+
let retries = 0;
4+
5+
const interval = setInterval(() => {
6+
retries++;
7+
if(signal.aborted) {
8+
console.log(`abort called for test ${testNumber}`);
9+
clearInterval(interval);
10+
return;
11+
}
12+
13+
if(retries > 100) {
14+
clearInterval(interval);
15+
throw new Error(`abort was not called for test ${testNumber}`);
16+
}
17+
}, 10);
18+
}
19+
}

test/parallel/test-runner-run.mjs

+93-18
Original file line numberDiff line numberDiff line change
@@ -118,29 +118,104 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
118118
assert.strictEqual(result[5], 'ok 2 - this should be executed\n');
119119
});
120120

121-
it('should stop watch mode when abortSignal aborts', async () => {
121+
it('should emit "test:watch:drained" event on watch mode', async () => {
122122
const controller = new AbortController();
123-
const result = await run({ files: [join(testFixtures, 'test/random.cjs')], watch: true, signal: controller.signal })
124-
.compose(async function* (source) {
125-
for await (const chunk of source) {
126-
if (chunk.type === 'test:pass') {
127-
controller.abort();
128-
yield chunk.data.name;
129-
}
130-
}
131-
})
132-
.toArray();
133-
assert.deepStrictEqual(result, ['this should pass']);
123+
await run({
124+
files: [join(testFixtures, 'test/random.cjs')],
125+
watch: true,
126+
signal: controller.signal,
127+
}).on('data', function({ type }) {
128+
if (type === 'test:watch:drained') {
129+
controller.abort();
130+
}
131+
});
134132
});
135133

136-
it('should emit "test:watch:drained" event on watch mode', async () => {
137-
const controller = new AbortController();
138-
await run({ files: [join(testFixtures, 'test/random.cjs')], watch: true, signal: controller.signal })
139-
.on('data', function({ type }) {
140-
if (type === 'test:watch:drained') {
141-
controller.abort();
134+
describe('AbortSignal', () => {
135+
it('should stop watch mode when abortSignal aborts', async () => {
136+
const controller = new AbortController();
137+
const result = await run({
138+
files: [join(testFixtures, 'test/random.cjs')],
139+
watch: true,
140+
signal: controller.signal,
141+
})
142+
.compose(async function* (source) {
143+
for await (const chunk of source) {
144+
if (chunk.type === 'test:pass') {
145+
controller.abort();
146+
yield chunk.data.name;
147+
}
148+
}
149+
})
150+
.toArray();
151+
assert.deepStrictEqual(result, ['this should pass']);
152+
});
153+
154+
it('should abort when test succeeded', async () => {
155+
const stream = run({
156+
files: [
157+
fixtures.path(
158+
'test-runner',
159+
'aborts',
160+
'successful-test-still-call-abort.js'
161+
),
162+
],
163+
});
164+
165+
let passedTestCount = 0;
166+
let failedTestCount = 0;
167+
168+
let output = '';
169+
for await (const data of stream) {
170+
if (data.type === 'test:stdout') {
171+
output += data.data.message.toString();
172+
}
173+
if (data.type === 'test:fail') {
174+
failedTestCount++;
142175
}
176+
if (data.type === 'test:pass') {
177+
passedTestCount++;
178+
}
179+
}
180+
181+
assert.match(output, /abort called for test 1/);
182+
assert.match(output, /abort called for test 2/);
183+
assert.strictEqual(failedTestCount, 0, new Error('no tests should fail'));
184+
assert.strictEqual(passedTestCount, 2);
185+
});
186+
187+
it('should abort when test failed', async () => {
188+
const stream = run({
189+
files: [
190+
fixtures.path(
191+
'test-runner',
192+
'aborts',
193+
'failed-test-still-call-abort.js'
194+
),
195+
],
143196
});
197+
198+
let passedTestCount = 0;
199+
let failedTestCount = 0;
200+
201+
let output = '';
202+
for await (const data of stream) {
203+
if (data.type === 'test:stdout') {
204+
output += data.data.message.toString();
205+
}
206+
if (data.type === 'test:fail') {
207+
failedTestCount++;
208+
}
209+
if (data.type === 'test:pass') {
210+
passedTestCount++;
211+
}
212+
}
213+
214+
assert.match(output, /abort called for test 1/);
215+
assert.match(output, /abort called for test 2/);
216+
assert.strictEqual(passedTestCount, 0, new Error('no tests should pass'));
217+
assert.strictEqual(failedTestCount, 2);
218+
});
144219
});
145220

146221
describe('sharding', () => {

0 commit comments

Comments
 (0)