Skip to content

Commit aa8053e

Browse files
MoLowdanielleadams
authored andcommitted
test_runner: recieve and pass AbortSignal
PR-URL: #43554 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 80c2fa8 commit aa8053e

File tree

9 files changed

+586
-81
lines changed

9 files changed

+586
-81
lines changed

doc/api/test.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ changes:
337337
* `only` {boolean} If truthy, and the test context is configured to run
338338
`only` tests, then this test will be run. Otherwise, the test is skipped.
339339
**Default:** `false`.
340+
* `signal` {AbortSignal} Allows aborting an in-progress test
340341
* `skip` {boolean|string} If truthy, the test is skipped. If a string is
341342
provided, that string is displayed in the test results as the reason for
342343
skipping the test. **Default:** `false`.
@@ -385,8 +386,9 @@ test('top level test', async (t) => {
385386
does not have a name.
386387
* `options` {Object} Configuration options for the suite.
387388
supports the same options as `test([name][, options][, fn])`
388-
* `fn` {Function} The function under suite.
389-
a synchronous function declaring all subtests and subsuites.
389+
* `fn` {Function|AsyncFunction} The function under suite
390+
declaring all subtests and subsuites.
391+
The first argument to this function is a [`SuiteContext`][] object.
390392
**Default:** A no-op function.
391393
* Returns: `undefined`.
392394

@@ -483,6 +485,20 @@ test('top level test', (t) => {
483485
});
484486
```
485487

488+
### `context.signal`
489+
490+
<!-- YAML
491+
added: REPLACEME
492+
-->
493+
494+
* <AbortSignal> Can be used to abort test subtasks when the test has been aborted.
495+
496+
```js
497+
test('top level test', async (t) => {
498+
await fetch('some/uri', { signal: t.signal });
499+
});
500+
```
501+
486502
### `context.skip([message])`
487503

488504
<!-- YAML
@@ -573,9 +589,28 @@ test('top level test', async (t) => {
573589
});
574590
```
575591

592+
## Class: `SuiteContext`
593+
594+
<!-- YAML
595+
added: REPLACEME
596+
-->
597+
598+
An instance of `SuiteContext` is passed to each suite function in order to
599+
interact with the test runner. However, the `SuiteContext` constructor is not
600+
exposed as part of the API.
601+
602+
### `context.signal`
603+
604+
<!-- YAML
605+
added: REPLACEME
606+
-->
607+
608+
* <AbortSignal> Can be used to abort test subtasks when the test has been aborted.
609+
576610
[TAP]: https://testanything.org/
577611
[`--test-only`]: cli.md#--test-only
578612
[`--test`]: cli.md#--test
613+
[`SuiteContext`]: #class-suitecontext
579614
[`TestContext`]: #class-testcontext
580615
[`test()`]: #testname-options-fn
581616
[describe options]: #describename-options-fn

lib/internal/main/test_runner.js

Lines changed: 35 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,18 @@ const {
33
ArrayFrom,
44
ArrayPrototypeFilter,
55
ArrayPrototypeIncludes,
6+
ArrayPrototypeJoin,
67
ArrayPrototypePush,
78
ArrayPrototypeSlice,
89
ArrayPrototypeSort,
9-
Promise,
10-
PromiseAll,
11-
SafeArrayIterator,
10+
SafePromiseAll,
1211
SafeSet,
1312
} = primordials;
1413
const {
1514
prepareMainThreadExecution,
1615
} = require('internal/bootstrap/pre_execution');
1716
const { spawn } = require('child_process');
1817
const { readdirSync, statSync } = require('fs');
19-
const { finished } = require('internal/streams/end-of-stream');
2018
const console = require('internal/console/global');
2119
const {
2220
codes: {
@@ -30,6 +28,7 @@ const {
3028
doesPathMatchFilter,
3129
} = require('internal/test_runner/utils');
3230
const { basename, join, resolve } = require('path');
31+
const { once } = require('events');
3332
const kFilterArgs = ['--test'];
3433

3534
prepareMainThreadExecution(false);
@@ -102,53 +101,39 @@ function filterExecArgv(arg) {
102101
}
103102

104103
function runTestFile(path) {
105-
return test(path, () => {
106-
return new Promise((resolve, reject) => {
107-
const args = ArrayPrototypeFilter(process.execArgv, filterExecArgv);
108-
ArrayPrototypePush(args, path);
109-
110-
const child = spawn(process.execPath, args);
111-
// TODO(cjihrig): Implement a TAP parser to read the child's stdout
112-
// instead of just displaying it all if the child fails.
113-
let stdout = '';
114-
let stderr = '';
115-
let err;
116-
117-
child.on('error', (error) => {
118-
err = error;
119-
});
120-
121-
child.stdout.setEncoding('utf8');
122-
child.stderr.setEncoding('utf8');
123-
124-
child.stdout.on('data', (chunk) => {
125-
stdout += chunk;
126-
});
127-
128-
child.stderr.on('data', (chunk) => {
129-
stderr += chunk;
130-
});
131-
132-
child.once('exit', async (code, signal) => {
133-
if (code !== 0 || signal !== null) {
134-
if (!err) {
135-
await PromiseAll(new SafeArrayIterator([finished(child.stderr), finished(child.stdout)]));
136-
err = new ERR_TEST_FAILURE('test failed', kSubtestsFailed);
137-
err.exitCode = code;
138-
err.signal = signal;
139-
err.stdout = stdout;
140-
err.stderr = stderr;
141-
// The stack will not be useful since the failures came from tests
142-
// in a child process.
143-
err.stack = undefined;
144-
}
145-
146-
return reject(err);
147-
}
148-
149-
resolve();
150-
});
104+
return test(path, async (t) => {
105+
const args = ArrayPrototypeFilter(process.execArgv, filterExecArgv);
106+
ArrayPrototypePush(args, path);
107+
108+
const child = spawn(process.execPath, args, { signal: t.signal, encoding: 'utf8' });
109+
// TODO(cjihrig): Implement a TAP parser to read the child's stdout
110+
// instead of just displaying it all if the child fails.
111+
let err;
112+
113+
child.on('error', (error) => {
114+
err = error;
151115
});
116+
117+
const { 0: { code, signal }, 1: stdout, 2: stderr } = await SafePromiseAll([
118+
once(child, 'exit', { signal: t.signal }),
119+
child.stdout.toArray({ signal: t.signal }),
120+
child.stderr.toArray({ signal: t.signal }),
121+
]);
122+
123+
if (code !== 0 || signal !== null) {
124+
if (!err) {
125+
err = new ERR_TEST_FAILURE('test failed', kSubtestsFailed);
126+
err.exitCode = code;
127+
err.signal = signal;
128+
err.stdout = ArrayPrototypeJoin(stdout, '');
129+
err.stderr = ArrayPrototypeJoin(stderr, '');
130+
// The stack will not be useful since the failures came from tests
131+
// in a child process.
132+
err.stack = undefined;
133+
}
134+
135+
throw err;
136+
}
152137
});
153138
}
154139

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy