Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit aa8053e

Browse filesBrowse 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
Copy full SHA for aa8053e

File tree

Expand file treeCollapse file tree

9 files changed

+586
-81
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

9 files changed

+586
-81
lines changed
Open diff view settings
Collapse file

‎doc/api/test.md‎

Copy file name to clipboardExpand all lines: doc/api/test.md
+37-2Lines changed: 37 additions & 2 deletions
  • Display the source diff
  • Display the rich diff
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
Collapse file

‎lib/internal/main/test_runner.js‎

Copy file name to clipboardExpand all lines: lib/internal/main/test_runner.js
+35-50Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.