Skip to content

Commit 3b73da3

Browse files
committed
test_runner: recieve and pass AbortSignal
1 parent 660d17d commit 3b73da3

File tree

9 files changed

+586
-82
lines changed

9 files changed

+586
-82
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+
this is an <AbortSignal> used to signal 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+
this is an <AbortSignal> used to signal 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: 36 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,14 @@ const {
66
ArrayPrototypePush,
77
ArrayPrototypeSlice,
88
ArrayPrototypeSort,
9-
Promise,
10-
PromiseAll,
11-
SafeArrayIterator,
9+
SafePromiseAll,
1210
SafeSet,
1311
} = primordials;
1412
const {
1513
prepareMainThreadExecution,
1614
} = require('internal/bootstrap/pre_execution');
1715
const { spawn } = require('child_process');
1816
const { readdirSync, statSync } = require('fs');
19-
const { finished } = require('internal/streams/end-of-stream');
2017
const console = require('internal/console/global');
2118
const {
2219
codes: {
@@ -30,6 +27,7 @@ const {
3027
doesPathMatchFilter,
3128
} = require('internal/test_runner/utils');
3229
const { basename, join, resolve } = require('path');
30+
const { once } = require('events');
3331
const kFilterArgs = ['--test'];
3432

3533
prepareMainThreadExecution(false);
@@ -102,53 +100,41 @@ function filterExecArgv(arg) {
102100
}
103101

104102
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-
});
103+
return test(path, async (t) => {
104+
const args = ArrayPrototypeFilter(process.execArgv, filterExecArgv);
105+
ArrayPrototypePush(args, path);
106+
107+
const child = spawn(process.execPath, args, { signal: t.signal });
108+
// TODO(cjihrig): Implement a TAP parser to read the child's stdout
109+
// instead of just displaying it all if the child fails.
110+
let err;
111+
112+
child.on('error', (error) => {
113+
err = error;
151114
});
115+
116+
child.stdout.setEncoding('utf8');
117+
child.stderr.setEncoding('utf8');
118+
const { 0: { code, signal }, 1: stdout, 2: stderr } = await SafePromiseAll([
119+
once(child, 'exit', { signal: t.signal }),
120+
child.stdout.toArray({ signal: t.signal }),
121+
child.stderr.toArray({ signal: t.signal }),
122+
]);
123+
124+
if (code !== 0 || signal !== null) {
125+
if (!err) {
126+
err = new ERR_TEST_FAILURE('test failed', kSubtestsFailed);
127+
err.exitCode = code;
128+
err.signal = signal;
129+
err.stdout = stdout.join('');
130+
err.stderr = stderr.join('');
131+
// The stack will not be useful since the failures came from tests
132+
// in a child process.
133+
err.stack = undefined;
134+
}
135+
136+
throw err;
137+
}
152138
});
153139
}
154140

0 commit comments

Comments
 (0)